• [讲座&活动公告] 【直播】GaussDB for MySQL关键特性发布和技术解读
    直播时间:2022/3/22 19:00-20:30直播嘉宾:佳恩 华为云数据库高级产品经理直播链接:https://bbs.huaweicloud.com/live/cloud_live/202203221900.html 直播简介:本次直播GaussDB(for MySQL)将正式发布HTAP混合负载特性,复杂查询效率提升百倍,让企业决策更加快速,准确。
  • [技术干货] 如何使用pg_chameleon迁移MySQL数据库至openGauss
    pg_chameleon介绍pg_chameleon是一个用Python 3编写的实时复制工具,经过内部适配,目前支持MySQL迁移到openGauss。工具使用mysql-replication库从MySQL中提取row images,这些row images将以jsonb格式被存储到openGauss中。在openGauss中会执行一个pl/pgsql函数,解码jsonb并将更改重演到openGauss。同时,工具通过一次初始化配置,使用只读模式,将MySQL的全量数据拉取到openGauss,使得该工具提供了初始全量数据的复制以及后续增量数据的实时在线复制功能。pg_chameleon的特色包括:通过读取MySQL的binlog,提供实时在线复制的功能。支持从多个MySQL schema读取数据,并将其恢复到目标openGauss数据库中。源schema和目标schema可以使用不同的名称。通过守护进程实现实时复制,包含两个子进程,一个负责读取MySQL侧的日志,一个负责在openGauss侧重演变更。使用pg_chameleon将MySQL数据库迁移至openGauss,通过pg_chameleon的实时复制能力,可以大大降低系统切换数据库时的停服时间。pg_chameleon在openGauss上的使用注意事项pg_chameleon依赖psycopg2,psycopg2内部通过pg_config检查PostgreSQL版本号,限制低版本PostgreSQL使用该驱动。而openGauss的pg_config返回的是openGauss的版本号(当前是 openGauss 2.0.0),会导致该驱动报版本错误,“Psycopg requires PostgreSQL client library (libpq) >= 9.1”。解决方案为通过源码编译使用psycopg2,并去掉源码头文件 psycopg/psycopg.h 中的相关限制。pg_chameleon通过设置LOCK_TIMEOUT GUC参数限制在PostgreSQL中的等锁的超时时间。openGauss不支持该参数(openGauss支持类似的GUC参数lockwait_timeout,但是需要管理员权限设置)。需要将pg_chameleon源码中的相关设置去掉。pg_chameleon用到了upsert语法,用来指定发生违反约束时的替换动作。openGauss支持的upsert功能语法与PostgreSQL的语法不同。openGauss的语法是 ON DUPLICATE KEY UPDATE { column_name = { expression | DEFAULT } } [, ...]。PostgreSQL的语法是 ON CONFLICT [ conflict_target ] DO UPDATE SET { column_name = { expression | DEFAULT } }。两者在功能和语法上略有差异。需要修改pg_chameleon源码中相关的upsert语句。pg_chameleon用到了CREATE SCHEMA IF NOT EXISTS、CREATE INDEX IF NOT EXISTS语法。openGauss不支持SCHEMA和INDEX的IF NOT EXISTS选项。需要修改成先判断SCHEMA和INDEX是否存在,然后再创建的逻辑。openGauss对于数组的范围选择,使用的是 column_name[start, end] 的方式。而PostgreSQL使用的是 column_name[start : end] 的方式。需要修改pg_chameleon源码中关于数组的范围选择方式。pg_chameleon使用了继承表(INHERITS)功能,而当前openGauss不支持继承表。需要改写使用到继承表的SQL语句和表。接下来我们将演示如何使用pg_chameleon迁移MySQL数据库至openGauss。配置pg_chameleonpg_chameleon通过~/.pg_chameleon/configuration下的配置文件config-example.yaml定义迁移过程中的各项配置。整个配置文件大约分成四个部分,分别是全局设置、类型重载、目标数据库连接设置、源数据库设置。全局设置主要定义log文件路径、log等级等。类型重载让用户可以自定义类型转换规则,允许用户覆盖已有的默认转换规则。目标数据库连接设置用于配置连接至openGauss的连接参数。源数据库设置定义连接至MySQL的连接参数以及其他复制过程中的可配置项目。详细的配置项解读,可查看官网的说明:https://pgchameleon.org/documents_v2/configuration_file.html下面是一份配置文件示例:# global settings pid_dir: '~/.pg_chameleon/pid/' log_dir: '~/.pg_chameleon/logs/' log_dest: file log_level: info log_days_keep: 10 rollbar_key: '' rollbar_env: '' # type_override allows the user to override the default type conversion # into a different one. type_override: "tinyint(1)": override_to: boolean override_tables: - "*" # postgres destination connection pg_conn: host: "1.1.1.1" port: "5432" user: "opengauss_test" password: "password_123" database: "opengauss_database" charset: "utf8" sources: mysql: db_conn: host: "1.1.1.1" port: "3306" user: "mysql_test" password: "password123" charset: 'utf8' connect_timeout: 10 schema_mappings: mysql_database:sch_mysql_database limit_tables: skip_tables: grant_select_to: - usr_migration lock_timeout: "120s" my_server_id: 1 replica_batch_size: 10000 replay_max_rows: 10000 batch_retention: '1 day' copy_max_memory: "300M" copy_mode: 'file' out_dir: /tmp sleep_loop: 1 on_error_replay: continue on_error_read: continue auto_maintenance: "disabled" gtid_enable: false type: mysql keep_existing_schema: No以上配置文件的含义是,迁移数据时,MySQL侧使用的用户名密码分别是 mysql_test 和 password123。MySQL服务器的IP和port分别是1.1.1.1和3306,待迁移的数据库是mysql_database。openGauss侧使用的用户名密码分别是 opengauss_test 和 password_123。openGauss服务器的IP和port分别是1.1.1.1和5432,目标数据库是opengauss_database,同时会在opengauss_database下创建sch_mysql_database schema,迁移的表都将位于该schema下。需要注意的是,这里使用的用户需要有远程连接MySQL和openGauss的权限,以及对对应数据库的读写权限。同时对于openGauss,运行pg_chameleon所在的机器需要在openGauss的远程访问白名单中。对于MySQL,用户还需要有RELOAD、REPLICATION CLIENT、REPLICATION SLAVE的权限。下面开始介绍整个迁移的步骤。创建用户及database在openGauss侧创建迁移时需要用到的用户以及database。在MySQL侧创建迁移时需要用到的用户并赋予相关权限。开启MySQL的复制功能修改MySQL的配置文件,一般是/etc/my.cnf或者是 /etc/my.cnf.d/ 文件夹下的cnf配置文件。在[mysqld] 配置块下修改如下配置(若没有mysqld配置块,新增即可):[mysqld] binlog_format= ROW log_bin = mysql-bin server_id = 1 binlog_row_image=FULL expire_logs_days = 10修改完毕后需要重启MySQL使配置生效。运行pg_chameleon进行数据迁移1. 创建python虚拟环境并激活python3 -m venv venv source venv/bin/activate2. 下载安装psycopg2和pg_chameleon更新pip:pip install pip --upgrade将openGauss的 pg_config 工具所在文件夹加入到 $PATH 环境变量中。例如:export PATH={openGauss-server}/dest/bin:$PATH下载psycopg2源码(https://github.com/psycopg/psycopg2 ),去掉检查PostgreSQL版本的限制,使用 python setup.py install编译安装。下载pg_chameleon源码(https://github.com/the4thdoctor/pg_chameleon ),修改前面提到的在openGauss上的问题,使用 python setup.py install编译安装。3. 创建pg_chameleon配置文件目录chameleon set_configuration_files4. 修改pg_chameleon配置文件cd ~/.pg_chameleon/configuration cp config-example.yml default.yml根据实际情况修改 default.yml 文件中的内容。重点修改pg_conn和mysql中的连接配置信息,用户信息,数据库信息,schema映射关系。前面已给出一份配置文件示例供参考。5. 初始化复制流chameleon create_replica_schema --config default chameleon add_source --config default --source mysql此步骤将在openGauss侧创建用于复制过程的辅助schema和表。6. 复制基础数据chameleon init_replica --config default --source mysql做完此步骤后,将把MySQL当前的全量数据复制到openGauss。可以在openGauss侧查看全量数据复制后的情况。7. 开启在线实时复制chameleon start_replica --config default --source mysql开启实时复制后,在MySQL侧插入一条数据:在openGauss侧查看 test_decimal 表的数据:可以看到新插入的数据在openGauss侧成功被复制过来了。8. 停止在线复制chameleon stop_replica --config default --source mysql chameleon detach_replica --config default --source mysql chameleon drop_replica_schema --config default
  • [技术干货] MySQL调优指导书
    目录-1 调优概述 -1.1 方案介绍 -1.2 调优思路 -1.2.1 业务流程 -1.2.2 调优思路 -2 调优方案 -2.1 BIOS配置 -2.2 操作系统调优 -2.2.1 文件系统调优 -2.2.2 网卡中断绑核 -2.2.3 IO参数调优 -2.2.4 缓存参数调优 -2.3 产品调优 -2.3.1 基于鲲鹏BoostKit特性进行调优 -3 调优效果汇总1 调优概述1.1 方案介绍       MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。      MySQL是一种关系型数据库管理系统,关系数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。      MySQL所使用的 SQL 语言是用于访问数据库的最常用标准化语言。MySQL 软件采用了双授权政策,分为社区版和商业版,由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特点,一般中小型网站的开发都选择 MySQL 作为网站数据库。1.2 调优思路 1.2.1 调优思路      下面介绍MySQL数据库具体的调优思路和分析过程,如图1-1所示。         图1-1 MySQL数据库调优思路       调优分析思路如下:很多情况下压测流量并没有完全进入到服务端,在网络上可能就会出现由于各种规格(带宽、最大连接数、新建连接数等)限制,导致压测结果达不到预期。接着看关键指标是否满足要求,如果不满足,需要确定是哪个地方有问题,一般情况下,服务器端问题可能性比较大,也有可能是客户端问题(这种情况比较小)。 对于服务器端问题,需要定位的是硬件相关指标,例如CPU,Memory,Disk I/O,Network I/O,如果是某个硬件指标有问题,需要深入的进行分析。如果硬件指标都没有问题,需要查看数据库相关指标,例如:等待事件、内存命中率等。如果以上指标都正常,应用程序的算法、缓冲、缓存、同步或异步可能有问题,需要具体深入的分析。     可能的瓶颈点如表1-1所示:      表1-1 可能的瓶颈点瓶颈点说明硬件/规格一般指的是CPU、内存、磁盘I/O方面的问题,分为服务器硬件瓶颈、网络瓶颈(对局域网可以不考虑)。操作系统一般指的是Windows、UNIX、Linux等操作系统。例如,在进行性能测试,出现物理内存不足时,虚拟内存设置也不合理,虚拟内存的交换效率就会大大降低,从而导致行为的响应时间大大增加,这时认为操作系统上出现性能瓶颈。数据库一般指的是数据库配置等方面的问题。例如,由于参数配置不合理,导致数据库处理速度慢的问题,可认为是数据库层面的的问题。 2 调优方案2.1 BIOS配置目的:      关闭CPU预取:CPU将内存中的数据读到CPU的高速缓冲Cache时,会根据局部性原理,除了读取本次要访问的数据,还会预取本次数据的周边数据到Cache里面,如果预取的数据是下次要访问的数据,那么性能会提升,如果预取的数据不是下次要取的数据,那么会浪费内存带宽。对于数据比较集中的场景,预取的命中率高,适合打开CPU预取,反之需要关闭CPU预取。      关闭SMMU:SMMU和MMU功能一样,为device设备提供地址转换功能,同时提供读写权限、Cache属性,更可以共页表。如果SMMU全局接口关闭,地址不经过翻译直接bypass传输,可以在特定场景上可以有效提升服务器的性能。 方法:1)    关闭SMMU。    a. 重启服务器过程中,单击Delete键进入BIOS,选择“Advanced > MISC Config”,单击Enter键进入。    b. 将“Support Smmu”设置为“Disable” 。2)  关闭CPU预取。   a.在BIOS中,选择“Advanced>MISC Config”,单击Enter键进入。   b.将“CPU Prefetching Configuration”设置为“Disabled”,单击F10键保存退出。3) 调优后,数据库OLTP查询性能提升在20%-30%之间。2.2 操作系统调优2.2.1 文件系统调优目的:       对于不同的IO设备,通过调整文件系统相关参数配置,可以有效提升服务器性能。方法:      在文件系统的mount参数上加上noatime,nobarrier两个选项。命令为(其中数据盘以及数据目录以实际为准):mount -o noatime,nobarrier /dev/nvme0n1p11.    一般来说,Linux会给文件记录了三个时间,change time, modify time和access time。access time指文件最后一次被读取的时间。modify time指的是文件的文本内容最后发生变化的时间。 change time指的是文件的inode最后发生变化(比如位置、用户属性、组属性等)的时间。       一般来说,文件都是读多写少,而且我们也很少关心某一个文件最近什么时间被访问了。所以,我们建议采用noatime选项,文件系统在程序访问对应的文件或者文件夹时,不会更新对应的access time。这样文件系统不记录access time,避免浪费资源。2.    现在的很多文件系统会在数据提交时强制底层设备刷新cache,避免数据丢失,称之为write barriers。但是,其实我们数据库服务器底层存储设备要么采用RAID卡,RAID卡本身的电池可以掉电保护;要么采用Flash卡,它也有自我保护机制,保证数据不会丢失。所以我们可以安全的使用nobarrier挂载文件系统。对于ext3, ext4和 reiserfs文件系统可以在mount时指定barrier=0。对于xfs可以指定nobarrier选项。说明:       调整后,性能提升并不明显。2.2.2 网卡中断绑核目的:       手动绑定网卡中断,根据网卡所属CPU将其进行分配,从而优化系统网络性能。方法:       查询网卡所在的CPU,将网络中断绑定到该CPU的所有核上。# 步骤 1 关闭irqbalance。 # 停止irqbalance服务 systemctl stop irqbalance.service # 关闭irqbalance服务 systemctl disable irqbalance.service # 查看irqbalance服务状态,确认服务已经关闭 systemctl status irqbalance.service # 步骤 2 查询中断号 cat /proc/interrupts | grep enp6s0 | awk -F ':' '{print $1}' # 步骤 3 根据中断号,将每个中断各绑定在一个核上。 echo $cpunum > /proc/irq/$irq/smp_affinity_list说明:在万兆网卡上绑核。压测机绑16核,测试主机绑24核压测机截图测试主机截图     3.网络中断绑核后,OLTP查询性能提升约10%。2.2.3 IO参数调优目的:       调大nr_requests参数,提升磁盘吞吐量。方法:echo 2048 > /sys/block/${device}/queue/nr_requests说明:       调优后OLTP查询性能不明显2.2.4 缓存参数调优目的:      调小swappiness参数,更加积极的使用内存,而非swap分区。方法:      执行命令 vi /etc/sysctl.conf ,将 vm.swappiness = 1添加到文件底部,保存退出,执行命令sysctl -p使其生效。说明:      调优后OLTP查询性能不明显2.3 产品调优2.3.1 基于鲲鹏BoostKit特性进行调优目的:      基于鲲鹏boostkit特性,使用官网提供的patch包对MySQL源码打包编译,提升数据库OLTP场景整体性能。方法:     1.      下载MySQL 8.0.20源码。                在MySQL官网下载页下载mysql-boost-8.0.20.tar.gz。      2.      解压源码包。tar -zxvf mysql-boost-8.0.20.tar.gz     3.      初始化建立GIT管理信息。               解压源码后,在源码根目录,执行以下命令初始化建立git管理信息:git init git add -A git config user.email "123@example.com" git config user.name "123" git commit -m "Initial commit"补丁文件名   适配版本   推荐使用场景说明0001-SHARDED-LOCK-SYS.patchMySQL 8.0.20TPC-C  提供Lock-sys锁优化特性。0002-LOCK-FREE-TRX-SYS.patchMySQL 8.0.20SysBench写场景提供Trx-sys锁优化特性。要求先应用前置补丁0001-SHARDED-LOCK-SYS.patch0001-SCHED-AFFINITY.patchMySQL 8.0.20 TPC-C 提供线程调度特性。     编译前需安装额外依赖,见依赖安装    4.      合入补丁。               表1 调优补丁列表               下载表1对应版本的补丁(例如Patch名为0001-SHARDED-LOCK-SYS.patch),将补丁解压到MySQL源码的根目录,执行以下命令生效补丁。git am --whitespace=nowarn 0001-SHARDED-LOCK-SYS.patch              如无报错信息,则补丁应用成功,类似下图    5.编译安装MySQL           说明:                  调优后,OLTP查询性能提升约20%。3 调优效果汇总序号调优方案具体措施性能指标1(主要)性能指标2(次要)性能提升0数据库迁移至鲲鹏2280V216W无基准数据1OLTP优化将mysql的patch包与源码共同编译24W无提升较大2BIOS关闭CPU预存取和smmu30W无提升较大3文件系统调优调整系统相关参数30W无提升较大4网卡中断绑核关闭irqbalance服务,测试机进行网卡绑核34W无基本不变5IO参数调优调大nr_requests参数,提升磁盘吞吐量34W无基本不变6缓存参数调优调小swappiness参数,更加积极的使用内存34W无最终数据 
  • [技术干货] 如何从头到脚彻底解决一个MySQL Bug?华为云数据库高级专家带你看【转载】
    说明:本文中的MySQL,如果不做特殊说明,指的是开源社区版MySQL。华为云数据库新版本在发布之前,会面临一系列严苛的测试规则,除了要求通过MySQL的所有测试用例之外,还需要通过由华为百万级更丰富、更贴近用户业务场景的测试用例构筑的测试防护网,以此充分验证新版本是否满足用户经典场景的稳定性。  正是在这样严苛的验证过程中,我们发现了MySQL的一个潜在Bug。   Bug描述测试环境: 基于相同的测试用例、数据集,分别测试MySQL 8.0.22, MySQL 8.0.26,与华为云GaussDB(for MySQL)的返回结果。  测试语句: select subq_0.c2 as c0 from (select ref_6.C_STATE asc0, case whenref_6.C_PHONE is not NULL then ref_5.C_ID else ref_5.C_ID end asc1, floor( ref_3.c_id)as c2 from sqltester.t0_hash_partition_p1_view as ref_0 right join sqltester.t4 as ref_1 on (EXISTS ( select ref_1.c_middle as c0 from sqltester.t1 as ref_2 where ((false) and ((true) or (true))) or (false) )) innerjoin sqltester.t0_range_key_subpartition_sub_view as ref_3 on(EXISTS ( select ref_0.c_credit as c0, ref_1.c_street_1 as c1, ref_4.c_credit_lim as c2, ref_3.c_credit as c3 from sqltester.t0_hash_partition_p1 as ref_4 where true )) left joinsqltester.t10 as ref_5 innerjoin sqltester.t11 as ref_6 on(true) on (((pi() isnot NULL)) and (false)) where (((ref_5.C_D_ID isnot NULL) or(ref_3.c_middle is not NULL)) )) as subq_0 where (EXISTS ( select subq_0.c0 as c0, pi() as c1, ref_11.c_street_1 as c2, ref_11.c_discount as c3, pi() as c4 from sqltester.t0_partition_sub_view_mixed_001 as ref_11)) group by 1 order by 1;返回结果: 如下图所示,MySQL 8.0.22、MySQL8.0.26与华为云GaussDB(for MySQL)的返回结果不一致,也就是说产生了Bug,如下图红色部分。 Bug分析首先确定哪一个执行结果是正确的。当前这个语句执行的execution plan是Hash Join,而MySQL8.0里面引入了Hash Join,由此推论开源版本可能存在问题。接下来我们从MySQL成熟版本以及非MySQL数据库两个方面来进行验证。   验证过程:使用相对成熟的版本MySQL 5.6进行验证,返回结果与GaussDB(for MySQL)相同,但与MySQL 8.0不同。使用PostgreSQL进行验证,执行结果与MySQL 5.6、GaussDB(for MySQL)相同,但与MySQL 8.0及更高版本不同。  由此可以确定:MySQL 8.0以及更高版本存在问题。   那么,是什么原因引起了这一Bug呢? 1.  首先精简查询,以方便后面分析。经过多次验证,将查询简化如下: SELECT count(*) FROM (SELECT 1 FROM sqltester.t4 AS ref_1 INNER JOIN sqltester.t4 AS ref_3 ON (EXISTS (SELECT 1 FROMsqltester.t4 AS ref_4 WHERE TRUE )) LEFT JOIN sqltester.t10 AS ref_5 ON (FALSE) WHERE (((ref_5.C_D_ID IS NOT NULL) OR (ref_3.c_middle IS NOT NULL))))AS subq_0 执行计划如下: -> Aggregate: count(0) (cost=2.75 rows=0) -> Filter: ((ref_5.C_D_ID is not null) or(ref_3.c_middle is null)) (cost=2.75 rows=0) -> Inner hash join(no condition) (cost=2.75 rows=0) -> Index scan on ref_3 using ndx_c_middle (cost=0.13 rows=50) -> Hash -> Inner hash join (no condition) (cost=1.50 rows=0) -> Index scan on ref_1 using ndx_c_id (cost=6.25 rows=50) -> Hash -> Left hash join (no condition) (cost=0.25 rows=0) -> Limit: 1 row(s) (cost=312.50 rows=1) ->Index scan on ref_4 using ndx_c_id (cost=312.50 rows=50) -> Hash -> Zero rows (Impossible filter) (cost=0.00..0.00 rows=0)从上面的执行计划可以看出,ref_5被优化器进行了优化,转换成了Zero rows,而且ref_5是Left Hash Join的内表。作为Left Join的内表,如果内表没有匹配条件的记录(这里已经是Impossible条件了,也就是说连接条件始终是False),则需要内表生成NULL行来和外表进行外表连接。   2.  在MySQL 8.0.22版本上执行问题查询,语句和执行结果如下: SELECT count(*) FROM (SELECT 1 FROM sqltester.t4 AS ref_1 INNER JOIN sqltester.t4 AS ref_3 ON (EXISTS (SELECT 1 FROM sqltester.t4 AS ref_4 WHERE TRUE )) LEFT JOIN sqltester.t10 AS ref_5 ON (FALSE) WHERE (((ref_5.C_D_ID IS NOT NULL) or(ref_3.c_middle IS NOT NULL))))AS subq_0; + + | count(*) | + + | 2500 | + + 1 row in set (0.00 sec)3.  对问题查询进行修改:去掉Where条件里面的另外一个条件(ref_3.c_middleis NULL)。 现在Where条件只包含了(ref_5.C_D_IDIS NOT NULL)一个条件,要求当前查询过滤掉所有ref_5没有匹配的连接记录。   则SQL语句和执行结果如下: SELECT count(*) FROM (SELECT 1 FROM sqltester.t4 AS ref_1 INNER JOIN sqltester.t4 AS ref_3 ON (EXISTS (SELECT 1 FROM sqltester.t4 AS ref_4 WHERE TRUE )) LEFT JOIN sqltester.t10 AS ref_5 ON (FALSE) WHERE (((ref_5.C_D_ID IS NOT NULL))))assubq_0; + + | count(*) | + + | 2500 | + + 1 row in set (0.01 sec)对比修改前后的语句和执行结果可以看出:执行结果与条件(ref_3.c_middle is NULL)没有关系,只与(ref_5.C_D_ID IS NOT NULL)这个条件有关。正常情况下对ref_5表来说,因为是Impossible条件,所以ref_5被优化成了Zero rows。那么如果只剩(ref_5.C_D_ID IS NOT NULL)这个条件,正常的结果应该是空集(count返回0)。但现在开源版本的结果集却不是,这再次说明了开源版本出现了问题。   对于Left Join来说,如果Join条件不匹配,内表需要设置为NULL行来连接外表。而这里执行计划使用的是Zero rows,也就是说MySQL 8.0使用的是ZeroRowsIterator来执行的。执行器需要调用ZeroRowsIterator::SetNullRowFlag来设置Nullflag。   4.  通过gdb来查看设置是否正确: Breakpoint 1, ZeroRowsIterator::SetNullRowFlag(this=0x7f92a413d510, is_null_row=false) at /mywork/mysql-sql/sql/basic_row_iterators.h:398 398 assert(m_child_iterator != nullptr); (gdb) n 399 m_child_iterator->SetNullRowFlag(is_null_row); (gdb) s std::unique_ptr<RowIterator,Destroy_only<RowIterator> >::operator-> (this=0x7f92a413d520) at/opt/simon/taurus/mysql-root/src/tools/gcc-9.3.0/include/c++/9.3.0/bits/unique_ptr.h:355 355 returnget(); (gdb) fin Run till exit from #0 std::unique_ptr<RowIterator,Destroy_only<RowIterator> >::operator-> ( this=0x7f92a413d520) at/opt/simon/taurus/mysql-root/src/tools/gcc-9.3.0/include/c++/9.3.0/bits/unique_ptr.h:355 ZeroRowsIterator::SetNullRowFlag (this=0x7f92a413d510,is_null_row=false) at/home/simon/mywork/mysql-sql/sql/basic_row_iterators.h:399 399 m_child_iterator->SetNullRowFlag(is_null_row); Value returned is $1 = (RowIterator *) 0x7f92a413d4d0 (gdb) s TableRowIterator::SetNullRowFlag (this=0x7f92a413d4d0,is_null_row=false) at/home/simon/mywork/mysql-sql/sql/records.cc:229 229 if(is_null_row) { (gdb) n 232 m_table->reset_null_row(); (gdb) 234 }从上面的gdb来看,断点处利用ZeroRowsIterator::SetNullRowFlag将表的Nullflag设置为了False。后面的gdb信息也证明了这一点。   可以确定,导致此Bug的原因是:ZeroRowsIterator::SetNullRowFlag设置为False这里是不正确的。因为如果把ZeroRowsIterator::SetNullRowFlag设置为False,那就会导致内表为ZeroRows的Left Join生成内表非NULL的结果集。 如何解决既然上面的Bug分析已经非常清楚了,那么修复起来也就比较简单了。只需要将ZeroRowsIterator::SetNullRowFlag始终设置为True就可以了。因为ZeroRowIterator只能产生两种结果,一种是空集,另一种就是作为外连接的内表产生NULL行。 对MySQL-8.0.26进行修复后,执行结果如下: 从返回的结果可以看出查询结果正确,也就是说问题得到了修复。   为了保障华为云GaussDB产品的可靠性,每一款产品发布前都要通过多轮严苛的测试用例。在发现问题后,华为云数据库团队以缜密的思路去逐步确定问题、分析问题,并第一时间修复Bug,解决问题,以确保客户的数据安全和业务结果的准确性。华为云数据库团队荟聚了业内50%以上的数据库内核专家,以专业技术实时保障客户业务安全,助力企业业务安全上云!
  • [数据库] MySQL-test 框架2
    MySQL test 框架参考https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_TESTING_TOOLS.html 测试框架程序文件• mysql-test-run.pl  测试主程序 调用mysqltest测试单个用例(单个测试文件)• mysqltest  测试单个用例,被mysql-test-run.pl调用• mysql_client_test  用来测试无法被mysqltest测试的MySQL client API• mysql-stress-test.pl  用于MySQL压力测试• unit-testing facility 用于创建测试存储引擎或插件的单独的单元测试测试suite程序文件所在目录• mysqltest 源码mysqltest.cc在client目录下,编译结果在bin目录下• mysql_client_test 源码mysql_client_test.cc在testclients目录下,编译结果在bin目录下• 其他测试程序 源码在mysql-test目录下,编译结果在install目录下的mysql-test目录install/mysql-test目录结构```bash-rw-r--r--. asan.suppdrwxr-xr-x. collections # 集成与发布测试时使用,保留在源码仓中以供参考drwxr-xr-x. extradrwxr-xr-x. include # 主要版本default_xxx.cnf文件及一些将被test文件包含的.inc文件drwxr-xr-x. lib # 保存了一些.pm .t .pl文件,将作为mysql-test-run.pl的模块被调用-rw-r--r--. lsan.supplrwxrwxrwx. mtr -> ./mysql-test-run.pl # 别名或副本-rwxr-xr-x. mysql-stress-test.pl # 压力测试lrwxrwxrwx. mysql-test-run -> ./mysql-test-run.pl # 别名或副本-rwxr-xr-x. mysql-test-run.pl # 用来一次测试drwxr-xr-x. r # 存放.result期望结果文件、.reject(与.result不一致的)实际结果文件-rw-r--r--. README-rw-r--r--. README.gcov-rw-r--r--. README.stressdrwxr-xr-x. std_data # 包含一些测试使用的数据文件drwxr-xr-x. suite # 每个子目录代表一个以文件件命名的test suitedrwxr-xr-x. t # 存放测试输入文件 .cnf .inc .opt .test等文件-rw-r--r--. valgrind.suppdrwxr-xr-x. var # 存放各种测试结果信息```t目录t包含了测试case的输入文件,对于一个用例ABC可能有文件ABC.cnf指定测试case的附加配置信息ABC-client.opt提供客户端的配置ABC-master.opt即使没有涉及主从复制,也加master,如果当前运行的server的配置和-master.opt的不一样,mysql-test-run.pl就会重启server;mysql-test-run.pl也会按照opt文件的配置重启server。每个bootstrap变量必须作为--initialize选项的参数,mysql-test-run.pl才能在服务器初始化的时候识别出需要使用的变量ABC-slave.opt有主从复制是才需要ABC.testABC.resultABC.combinations为每次测试case运行提供选项段ABC-master.sh在main server启动前将被执行,win不支持,将来可能被其他机制替换ABC-slave.sh在slave server启动前将被执行,win不支持,将来可能被其他机制替换suite.opt为所有该suite内的test case提供配置,如果一个test运行多个server,则suite.opt对所有这些server都有效。该文件中的选项会被-master.opt、-slave.opt中的同选项覆盖disabled.def用来配置将被延期或禁止运行的test case,如果由于server有bug致使一些test会失败,想忽略这些test,不被mysql-test-run.pl执行,可以将这些test列到这个文件中cnf文件可以包含基础或其他配置文件!include include/default_my.cnf[mysqld.1] # 可以使用.1/.2等组后缀名区分不同server组,每个server启动的时候都默认带有组后缀名(defaults-group-suffix)Options for server mysqld.1[mysqld.2]Options for server mysqld.2[mysqltest] # 测试客户端的配置选项ps-protocol.........[ENV] # 指定测试case的环境变量SERVER_MYPORT_1= @mysqld.1.port # 定义一个SERVER_MYPORT_1环境变量,值为上段mysqld.1中的port的值SERVER_MYPORT_2= @mysqld.2.portr目录可能包含的文件ABC.resultABC.test文件的期望输出内容ABC.reject如果test case是由于输出不一致而失败的(非其他原因失败),则.reject文件中包含test case的实际输出如果--check-testcases选项打开,若test文件没有对应result文件时,mtr将对其标记为失败。--check-testcases作用检查测试用例是否有副作用。 这是通过在每个测试用例之前和之后检查系统状态来完成的。 如果有任何差异,则测试用例因此被标记为失败。类似地,当启用 --check-testcases 选项时,MTR 会对丢失的 .result 文件进行额外检查,并且没有相应 .result 文件的测试用例被标记为失败。默认情况下启用此检查。 要禁用它,请使用 --nocheck-testcases 选项。var目录用来存放各种测试运行中生成的结果文件:log文件、temp文件、trace文件、Unix socket文件等。这个目录不能被同时跑的测试所共享。suite目录该目录下每个子文件夹代表一个与文件夹同名的test suite。 每个test suite可能包含如下部分• t目录• r目录• include目录• 一个combinations格式的文件,为每次测试运行提供配置段(Controlling the Binary Log Format Used for an Entire Test Run )• 一个my.cnf格式的文件,为本suite中的所有测试提供配置项,同配置项内容会被test_name.cnf文件的所覆盖。collections目录此目录包含我们在集成和发布测试期间运行的测试运行的集合。这些文件在此上下文之外没有直接用处,但需要成为源码仓的一部分并包含在内以供参考。每个文件包含零行或多行,每行都调用一次 mysql-test-run.pl。这些调用是这样编写的• 假设perl在环境搜索路径中• 原则上任何集合都可以作为shell脚本或批处理文件运行• mysql-test目录是当前工作目录。每行格式例如 perl mysql-test-run.pl --force --timer --big-test --testcase-timeout=90 --parallel=auto --experimental=collections/default.experimental --comment=normal-big --vardir=var-normal-big --report-features --skip-test-list=collections/disabled-daily.list --unit-tests-reportunittest目录单元测试相关目录,相关于存储引擎和插件的附加文件可能存在于storage或plugin目录的子目录下。在顶层Makefile中有多个targets可用于运行测试集。make test只运行单元测试(?),其他测试集见Makefile文件。 一个“test case”是单个文件,case中可能包含多个测试命令,任意一个测试命令没有产生预期的结果都认为整个测试用例失败(预期结果包括测试某种预期的错误,例如语法错误)。test case的输出内容(test result, 和.result文件进行diff)包括• 输入的SQL语句及其输出信息• mysqltest命令(例如echo、exec)的输出结果,而命令本身不输出到结果。disable_query_log和enable_query_log命令控制是否logging输出SQL语句(.result?) disable_result_log和enable_result_log命令控制是否logging输出SQL语句的结果包括warning、error信息(.result?)mysqltest默认从其标准输入读入test case,也可以使用--test-file或-X选项显式地给定一个test case文件名。mysqltest默认向其标准输出写入test case的结果,也可以使用--result-file或-R选项来显式地指定result文件的位置。 这个配置项和--record选项共同确定mysqltest如何处理一个test case的实际和预期测试结果。• 如果一个test没有输出result,mysqltest会带着error信息退出,除非--result-file指定的文件名为空• 如果--result-file没有给出,mysqltest将发送结果到标准输出• 如果有--result-file但没有--record选项mysqltest从指定的文件中读取期望的result文件,并且和期望的result结果做比较。如果结果不匹配,mysqltest就将实际结果写到log目录下.reject文件中,并error退出,只要有可用的diff工具,就会再输出实际与预期的diff结果。• 如果--result-file和--record都给出了,则mysqltest将用实际测试结果更新到给出的文件中,该文件不需要预先存在(最开始的result文件自动生成)。mysqltest程序本身对t/r目录一无所知,这些目录下的文件,约定由mysql-test-run.pl使用,由该pl文件为每个test case以适当的参数调用mysqltest,告诉mysqltest从哪里读取输入和向哪里输出。• 需要C++运行时库mysqltest和mysql_client_test程序是用C++编写的,可以在任何可以编译MySQL本身的系统上使用,或者可以使用二进制MySQL发行版。• 需要perl测试框架的其他部分,例如 mysql-test-run.pl 是 Perl 脚本,应该在安装了 Perl 的系统上运行。• 需要diffmysqltest使用diff程序来比较预期和实际测试结果。 如果未找到diff,mysqltest会写入错误消息并转储 .result 和 .reject 文件的全部内容,以便您可以尝试确定测试未成功的原因。 如果您的系统没有diff,您可以从以下站点之一获取它: http://www.gnu.org/software/diffutils/diffutils.html  http://gnuwin32.sourceforge.net/packages/diffutils.htm • 目录不能带空格如果从完整路径包含空格字符的目录中启动,mysql-test-run.pl 将无法正常运行,因为这将会使在所有引用这个路径的不同上下文中正确处理它变得很复杂。参考 https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_MYSQL_TEST_RUN_PL.html • 尽可能多收集错误信息,再上报bughttps://dev.mysql.com/doc/refman/8.0/en/bug-reports.html. • 确保包含了mysql-test-run.pl的输出、var/log中所有的.reject文件以及diff报告• 检查单独跑这个用例是否失败• cd mysql-test• ./mysql-test-run.pl test_name如果还失败,再继续检查是否自己编译MySQL使用了--with-debug选项且运行mysql-test-run.pl是否使用了--debug选项。如果这样还失败,则连带var/tmp/master.trace文件一起上报(顺带包含系统描述、mysqld版本、如何编译该mysqld文件)。• 运行mysql-test-run.pl带--force选项,查看是否还有其他test case失败。• Result length mismatch或者Result content mismatch,就表示可能有bug或该mysql版本在某些情况下产生的结果略有不同。• 如果一个test case完全失败,应该检查var/log目录中的logs文件中的错误信息。• 如果自己编译的debug版本MySQL,出现test case失败的情况,可以运行mysql-test-run.pl加--gdb和--debug选项来调试失败原因。在CMake时可以使用-DWITH_DEBUG来指定编译debug版本的MySQL
  • [数据库] MySQL-子查询分享
    1.1 子查询介绍         SQL支持创建子查询( subquery) ,就是嵌套在其他查询中的查询 ,也就是说在select语句中会出现其他的select语句,我们称为子查询或内查询。而外部的select语句,称主查询或外查询。 1.2 子查询分类1.2.1 按返回结果分类 标量子查询:返回单个标量值。需求:谁的年纪比Peter大?先查询Peter的年级 SELECT age FROM employees WHERE name = 'Peter';查询员工的信息,满足 age > ① 的结果 SELECT * FROM employees WHERE age > (  SELECT age  FROM employees  WHERE name = ' Peter' ); 列子查询:返回一个列,返回多行记录之中同一列的内容。1.  SELECT  2.      *   3.  FROM  4.      product   5.  WHERE  6.      product_id IN ( SELECT product_id FROM product_info );   行子查询:返回单行,这类子查询现在用的并不多。          需求:查询学生表中,年龄最大且身高最高的学生。 select * from student where-- 其中,(age, height) 称之为行元素(age, height) = (select max(age), max(height) from student); 表子查询:返回的是一个二维表,此种子查询出现在FROM子句中。         select * from (select * from t2 limit 2) as t; 1.2.2 按出现位置分类select型子查询       select后的子查询:仅仅支持标量子查询,即只能返回一个单值数据。       select (select a from t2 limit 1) from t1; from型子查询from型子查询即把内层sql语句查询的结果作为临时表供外层sql语句再次查询,所以支持的是表子查询。但是必须对子查询起别名,否则无法找到表。where或having型子查询 将内层查询结果当做外层查询的比较条件。支持标量子查询(单列单行)、列子查询(单列多行)、行子查询(多列多行)。 order by或group by型子查询         只支持标量子查询。         select * from t1 order by (select c from t4 where a = t1.a);         t1表按t4表的c列来排序。1.2.3 按相关性分非相关子查询         不相关子查询是独立于外部查询的子查询。相关子查询         相关子查询中查询条件依赖于外层查询。       select * from t1 where a > (select a from t2 where t2.b = t1.c);  1.2.4 按谓词分in子查询         IN子查询主要用于判断一个给定值是否存在于子查询的结果集中。内层查询语句返回一个数据列,这个数据列的值将供外层查询语句进行比较。 exists子查询EXIST子查询用于判断子查询的结果集是否为空, 该子查询实际上并不返回任何数据,而是返回值True或False,如果子查询存在返回数据,则exists返回True,反之返回False!            select * from select_student where exists (select 1);  all子查询 关键字 ALL 用于指定表达式需要与子查询结果集中的每个值都进行比较,当表达式与每个值都满足比较关系时,会返回 TRUE,否则返回 FALSE;select s1 from t1 where s1 > all (select s1 from t2); any或some子查询         SOME 和 ANY 是同义词,表示表达式只要与子查询结果集中的某个值满足比较关系,就返回 TRUE,否则返回 FALSE。       select s1 from t1 where s1 > any (select s1 from t2); 1.3 子查询在代码中的表示 MySQL中负责分析和存储一个select语句信息的数据结构是SELECT_LEX(st_select_lex)类,负责分析和存储union关系的数据结构是SELECT_LEX_UNIT (st_select_lex_unit)。下面以一个简单的SQL作为例子来讲解。例如: Select * from tt where tt.id in (select id from tt1) union select * from tt1;SQL在经过解析后的类间关系如下图:  标量子查询/ANY子查询Item_singlerow_subselect       exists子查询Item_exists_subselect in子查询Item_in_subselect ALL/ANY/SOME子查询Item_maxmin_subselectItem_allany_subselect from子查询用派生表表示TABLE_LIST  1.4 子查询执行流程explainid相同    执行顺序从上往下id不同    如果是子查询,id的序号会递增,id越大优先级越高,越先被执行。 from子查询先执行子查询后执行主查询 where不相关子查询先执行主查询,后执行子查询。子查询只执行一次。 where相关子查询先执行主查询,后执行子查询。主查询读取一行子查询都要执行一次。 
  • [数据库] MySQL-优化器分享
    1.1 查询优化对于数据库的使用来说,查询语言(SQL语言)是一种基于声明式的编程语言。使用过程中只你只需要制定你要达到什么目的,而并没有指明要怎么达到目的。因此这个“怎么做”的问题就成为了优化器的主要工作。所以优化器被称作数据库的大脑。优化器的作用:制定最优的执行计划,提升查询的性能,降低普通用户使用数据库调优的门槛。 查询树 —— 优化器 —— 计划树 查询树 —— 查询重写 ——查询树’ —— 路径生成 —— 最优路径 —— 计划生成 —— 计划树  逻辑优化(查询重写,代数优化): 主要依据关系代数的等价变换做一些等价逻辑变换。运用了关系代数规则和启发式规则。两个目标:         将查询转换为等价的、效率更高的形式,例如将效率低的谓词转换为效率高的谓词、消除重复条件等。         尽量将查询重写为等价、简单且不受表顺序限制的形式,为物理查询优化阶段提供更多的选择,如视图的重写、子查询的合并转换等。 物理优化(查询算法优化 非代数优化): 主要根据数据读取、表连接方式、表连接顺序、排序等技术对查询进行优化,运用了基于代价估算的多表连接算法求解最小花费的技术 查询优化目的就是生成最好的查询计划,策略通常有两个:基于规则优化(RBO):使用启发式规则制定出相应的执行计划。基于代价优化(CBO):主流数据库都采用了基于代价策略进行优化的技术。 基于规则优化具有操作简单且快速确定连接方式的优点,但这种方法只排除了一部分不好的可能路径,所以得到的结果未必是最好的。基于代价优化是对各种可能的情况进行量化比较,从而得到花费最少的情况,但如果组合情况很多则花费的判断时间就会很多。查询优化器的实现,多是两种优化策略的组合使用,如MySQL和PostgreSQL。  1.2 逻辑优化如何找出SQL语句等价的变换形式,使得SQL执行更高效。 从运算规则的角度考虑优化选择下推  1.2.1 谓词重写使用谓词查询条件的可满足性和可传递性进行化简 1.2.2 谓词下推优化将谓词查询条件往下推,提前过滤  1.2.3 谓词上移优化将谓词查询条件中比较繁重的函数计算放到最后,没期望减少繁重计算的次数达到提升性能的目的。 1.2.4 视图展开将视图物化替换成子查询,可以实现更多的优化。1.2.5 join相关重写优化消除冗余连接外连接转为内连接,从而减少关联处理产生的中间结果集  1.3 物理优化对多个可行的物理执行代价进行评估,选择最优的执行计划。1.3.1 物理优化阶段,主要解决的问题:从可选的单表扫描方式中,挑选什么样的单表扫描方式是最优的?对于两个表连接时,如何连接是最优的?对于多个表连接,连接顺序有多种组合,哪种连接顺序是最优的?对于多个表连接,连接顺序有多种组合,是否要对每种组合都探索?如果不全部探索,怎么找到最优的一种组合? 根据数据的分布(统计信息)情况来对查询执行路径进行评估,从可选的路径中选择一个执行代价最小的路径进行执行。 查询代价估算,代价估算模型:总代价 = IO代价 +  CPU代价 + (网络代价)COST = P * a_page_cpu_time + W * T;P: page数T: 元组数W:权重因子,又称选择率   代价估算,表扫描算子,连接算子,聚合算子,排序算子,不同算子具备不同的代价计算模型。  1.3.2 单表扫描方式顺序扫描 索引扫描,索引列的值选择率越低,索引越有效。判断是否可以走索引。1.3.3 两表连接算法嵌套循环连接,归并连接,hash连接hashjoin  如果表中连接列值重复率很高不能均匀分布,相同值的元组映射到少数几个桶中,hash连接算法的效率就不会高。如果内表太大,内存放不下,则需要写临时文件,会导致IO的颠簸。 1.3.4 路径搜索模型树的形成过程,主要有以下两种策略:至顶向下         cascade模型         不严格区分逻辑优化和物理优化两个阶段,使用同一的规则系统进行处理。         搜索空间能力较为高效,以结果为导向出发自顶向下进行搜索,可以较早的排除无用的路径分支,实现相对比较复杂。 至底向上         system-R模型         算法实现比较直观,不方便应用剪枝技巧,在查询中可能会遇到在父节点的某一种方案成本很高,后续完全无需考虑的情况,尽管如此,需要被利用的子计算都已经完成了,这部分计算因此不可避免。  1.3.5 多表连接算法 启发式算法是一个基于直观或经验的算法。在物理查询优化阶段常用的启发式规则有:关系R在列X上建立索引,且对R的选择操作发生在列X上,则采用索引扫描方式R连接S,其中一个关系上的连接列存在索引,则采用索引连接且此关系作为内表。R连接S,其中一个关系上的连接列是排序的,则采用排序进行连接比hash连接好。 动态规划算法从底向上进行,即从叶子(单表)开始算作第一层,然后由底层开始对每层的关系做两两连接,构造出上层,逐次递归推到树根。每层路径的生成都是基于上层生成的最优路径。 多表连接算法最大次数是N!,N=5,连接次数是120,N=10,连接次数是3628800。如何将搜索空间限制在一个可以接受的时间范围内,并高效地生成执行计划将成为一个难点。 优化器与统计密切相关基于代价的查询执行计划估算,依赖于被查询对象的各种数据,而数据是动态变化的,如果实时获取这些数据,系统计算的开销会比较大,所以一般是定期或根据需要统计这些数据。ANALYZE命令用于更新统计信息。 优化器与索引优化器需要利用索引提高表扫描效率。 1.4 MySQL查询优化器 MySQL查询优化器设计精巧,但层次不够清晰,没有把优化过程明显地分为逻辑优化和物理优化,而是互为混杂。 运用关系代数原理和启发式规则进行逻辑上的优化,使用代价估算模型进行物理上的优化。 MySQL查询优化器通过SELECT_LEX和JOIN对象的方法,如SELECT_LEX.prepare()、JOIN.optimize(),完成优化工作。 1.4.1 主要文件sql_resolver.cc优化器代码,查询语句预处理 sql_select.cc优化器代码,侧重于优化器的框架搭建 sql_optimizer.cc优化器代码,侧重于逻辑优化 sql_planner.cc优化器代码,侧重于物理优化 1.4.2 关键函数 void SELECT_LEX::remove_redundant_subquery_clauses(    THD *thd, int hidden_group_field_count)去除子查询中的冗余子句 SELECT_LEX::resolve_subquery(THD *thd)将子查询转换为半连接,将IN转换为EXISTS,将ALL/ANY转换为MIN/MAX等 子查询优化调用栈#0  Item_in_subselect::single_value_in_to_exists_transformer (this=0xfffed406e3f8, thd=0xfffed401ff10, select=0xfffed409d8e0, func=0x76937e8 <eq_creator>) at /home/hwsql-pq/code/olap-kp-dev/sql/item_subselect.cc:1897#1  0x000000000356ae14 in Item_in_subselect::single_value_transformer (this=0xfffed406e3f8, thd=0xfffed401ff10, select=0xfffed409d8e0, func=0x76937e8 <eq_creator>) at /home/hwsql-pq/code/olap-kp-dev/sql/item_subselect.cc:1849#2  0x000000000356d30c in Item_in_subselect::select_in_like_transformer (this=0xfffed406e3f8, thd=0xfffed401ff10, select=0xfffed409d8e0, func=0x76937e8 <eq_creator>) at /home/hwsql-pq/code/olap-kp-dev/sql/item_subselect.cc:2478#3  0x000000000356d064 in Item_in_subselect::select_transformer (this=0xfffed406e3f8, thd=0xfffed401ff10, select=0xfffed409d8e0) at /home/hwsql-pq/code/olap-kp-dev/sql/item_subselect.cc:2389#4  0x00000000031119c0 in SELECT_LEX::resolve_subquery (this=0xfffed409d8e0, thd=0xfffed401ff10) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_resolver.cc:1379#5  0x000000000310f130 in SELECT_LEX::prepare (this=0xfffed409d8e0, thd=0xfffed401ff10) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_resolver.cc:398#6  0x00000000031d06dc in SELECT_LEX_UNIT::prepare (this=0xfffed409d220, thd=0xfffed401ff10, sel_result=0xfffed409ea40, added_options=268435456, removed_options=0) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_union.cc:492#7  0x000000000356e868 in SubqueryWithResult::prepare (this=0xfffed409ea68, thd=0xfffed401ff10) at /home/hwsql-pq/code/olap-kp-dev/sql/item_subselect.cc:2876#8  0x00000000035666c4 in Item_subselect::fix_fields (this=0xfffed406e3f8, thd=0xfffed401ff10, ref=0xfffed406d2a0) at /home/hwsql-pq/code/olap-kp-dev/sql/item_subselect.cc:567#9  0x000000000356d72c in Item_in_subselect::fix_fields (this=0xfffed406e3f8, thd_arg=0xfffed401ff10, ref=0xfffed406d2a0) at /home/hwsql-pq/code/olap-kp-dev/sql/item_subselect.cc:2539#10 0x0000000003111edc in SELECT_LEX::setup_conds (this=0xfffed406d1c8, thd=0xfffed401ff10) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_resolver.cc:1481#11 0x000000000310eafc in SELECT_LEX::prepare (this=0xfffed406d1c8, thd=0xfffed401ff10) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_resolver.cc:269  SELECT_LEX::flatten_subqueries把子查询转换为半连接操作 bool SELECT_LEX::simplify_joins(THD *thd,                                mem_root_deque<TABLE_LIST *> *join_list,                                bool top, bool in_sj, Item **cond,                                uint *changelog) 外连接,嵌套连接优化。将外连接转为内连接,消除嵌套连接。 bool JOIN::optimize()优化器最主要函数 bool optimize_cond(THD *thd, Item **cond, COND_EQUAL **cond_equal,                   mem_root_deque<TABLE_LIST *> *join_list,                   Item::cond_result *cond_value)条件优化 bool Optimize_table_order::greedy_search(table_map remaining_tables)寻找最优路径 void Optimize_table_order::best_access_path(JOIN_TAB *tab,                                            const table_map remaining_tables,                                            const uint idx, bool disable_jbuf,                                            const double prefix_rowcount,                                            POSITION *pos)估算将要连接到查询树的表的最佳访问路径和花费 寻找最优路径调用栈#0  Optimize_table_order::best_access_path (this=0xffff819118c8, tab=0xfffed409d378, remaining_tables=1, idx=0, disable_jbuf=false, prefix_rowcount=1, pos=0xfffed409d540) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_planner.cc:973#1  0x00000000030dffcc in Optimize_table_order::best_extension_by_limited_search (this=0xffff819118c8, remaining_tables=1, idx=0, current_search_depth=62) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_planner.cc:2764#2  0x00000000030dedbc in Optimize_table_order::greedy_search (this=0xffff819118c8, remaining_tables=1) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_planner.cc:2320#3  0x00000000030de644 in Optimize_table_order::choose_table_order (this=0xffff819118c8) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_planner.cc:2004#4  0x00000000030444a0 in JOIN::make_join_plan (this=0xfffed409cdb0) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_optimizer.cc:5185#5  0x0000000003037a30 in JOIN::optimize (this=0xfffed409cdb0) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_optimizer.cc:653 1.4.3 代价计算代价计算类 class Cost_model_table { public:  Cost_model_table()      : m_cost_model_server(nullptr),        m_se_cost_constants(nullptr),        m_table(nullptr) {#if !defined(DBUG_OFF)    m_initialized = false;#endif  }} class Cost_estimate { private:  double io_cost;      ///< cost of I/O operations  double cpu_cost;     ///< cost of CPU operations  double import_cost;  ///< cost of remote operations  double mem_cost;     ///< memory used (bytes) double total_cost() const { return io_cost + cpu_cost + import_cost; } …} 代价计算调用栈#0  Cost_estimate::total_cost (this=0xffff819810d0) at /home/hwsql-pq/code/olap-kp-dev/sql/handler.h:3290#1  0x00000000030dbad0 in Optimize_table_order::calculate_scan_cost (this=0xffff819818c8, tab=0xfffee0e78c88, idx=0, best_ref=0x0, prefix_rowcount=1, found_condition=false, disable_jbuf=true, rows_after_filtering=0xffff81981178, trace_access_scan=0xffff81981180) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_planner.cc:833#2  0x00000000030dc64c in Optimize_table_order::best_access_path (this=0xffff819818c8, tab=0xfffee0e78c88, remaining_tables=1, idx=0, disable_jbuf=true, prefix_rowcount=1, pos=0xfffee0e78e50) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_planner.cc:1135#3  0x00000000030dffcc in Optimize_table_order::best_extension_by_limited_search (this=0xffff819818c8, remaining_tables=1, idx=0, current_search_depth=62) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_planner.cc:2764#4  0x00000000030dedbc in Optimize_table_order::greedy_search (this=0xffff819818c8, remaining_tables=1) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_planner.cc:2320#5  0x00000000030de644 in Optimize_table_order::choose_table_order (this=0xffff819818c8) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_planner.cc:2004#6  0x00000000030444a0 in JOIN::make_join_plan (this=0xfffee0e786c8) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_optimizer.cc:5185#7  0x0000000003037a30 in JOIN::optimize (this=0xfffee0e786c8) at /home/hwsql-pq/code/olap-kp-dev/sql/sql_optimizer.cc:653
  • [数据库] MySQL-单元测试介绍
    1.1 MySQL单元测试介绍 MySQL有两套单元测试工具,一个是tap,一个是gtest,现在用gtest。 tap:#include "unittest/mytap/tap.h" gtest: 1.2 目录结构单元测试主目录:unittest examples:tap参考用例目录gunit:gtest用例目录mytap:tap工具目录 gunit目录: 1.3 编译编译时要加上这个参数:-DENABLE_DOWNLOADS=1会自动下载gtest工具。 如果无法自动下载,自己下载googletest-release-1.8.1.zip放到source_downloads目录下,无需解压。 修改了原代码可能会导致编译不通过,需要修改单元测试的CMakeLists.txt或代码。 如何判断编译是否成功,进入unittest目录执行ctest命令1.4 测试方法单元测试要在build目录下执行,不能在install目录下执行。1.4.1 全量测试build主目录下执行ctest 1.4.2 执行gtest用例unittest/gunit下执行ctest 或 make test 1.4.3 执行单个测试集或单个用例文件runtime_output_directory1.4.4 执行单个用例 -h 查看帮助信息1.5 添加单元测试用例文件名要加-t ,没有加-t的文件编译成库文件供用例链接。  1.6 用例编写1.6.1 gtest使用略过 1.6.2 gmock使用包含头文件 使用步骤:定义一个Mock类: 创建一个Mock对象 定义Mock对象的行为EXPECT_CALL 执行测试代码 
  • [数据库] MySQL-插件化plugin分析
       mysql插件化开发mysql提供了一个mysql插件的开发模块,在源码的plugin目录中,有一个daemon_example文件夹,里面就是一个demo的插件。步骤一:定义插件mysql_declare_plugin(daemon_example){    MYSQL_DAEMON_PLUGIN, //插件的序号,需要在plugin.h中定义    &daemon_example_plugin, //插件的描述结构,可以有版本信息,回调函数信息等。    "daemon_example",  //插件的名字    PLUGIN_AUTHOR_ORACLE,  //插件的作者    "Daemon example, creates a heartbeat beat file in mysql-heartbeat.log",     //插件的简介描述    PLUGIN_LICENSE_GPL,  //协议    daemon_example_plugin_init,   /* Plugin Init 插件init函数*/    nullptr,                      /* Plugin Check uninstall */    daemon_example_plugin_deinit, /* Plugin Deinit 插件deinit函数*/    0x0100 /* 1.0 */,    nullptr, /* status variables                */    nullptr, /* system variables                */    nullptr, /* config options                  */    0,       /* flags                           */} mysql_declare_plugin_end;其中parallel_query_plugin是自定义的结构,可以传递信息。步骤二:定义init函数 deinit函数暂时都只有一个printf函数。步骤三:编写cmake文件   进阶一:传递参数在插件的标准init函数中,有一个void* p参数,在调用的时候,实际传递的是st_plugin_int结构。在plugin_initialize函数中通过函数指针的方式调用init函数,传递这个结构给函数。 其中st_mysql_plugin就是定义的插件那个结构体。初始化的调用栈: 比如在init函数内使用参数:使用了st_plugin_int的pligin_dl变量,使用了plugin的type变量和info变量。Info变量是一个指针,指向自己的定义的数据结构。 打印输出: 进阶二:回调函数可以参考clone的这个plugin的写法。使用my_plugin_lock_by_name找到plugin的描述,进而找到定义的struct ,然后可以调用struct里面的东西,比如变量,比如函数指针。   插件操作查看插件show plugins;安装插件INSTALL PLUGIN parallel_query SONAME 'libparallel_query.so';          、打印了输出:同时show plugin可以看到插件名字卸载插件UNINSTALL PLUGIN  parallel_query; 插件被触发: 代码patch问题Install plugin错误原因:修改地方不全,除plugin文件中增加插件宏定义后,需要修改其他地方Sql_plugin.cc的min_plugin_info_interface_version cur_plugin_info_interface_version plugin_type_names这三个数组需要同步修改   加载插件的时候没有打印信息原因:在my.cnf文件中的mysqld参数中加了log-error参数。  导致在启动的时候就不会有信息打印在屏幕上。 
  • [数据库] MySQL-truncate问题分析和优化
    一:问题描述1.问题描述:truncate清理数据影响联机交易性能,官方对truncate相关问题的描述:二:问题复现完成mysql 5.7.27安装并按如下配置文件启动数据库My.cnf:[mysqld_safe]log-error=/data/mysql/log/mysql.logpid-file=/data/mysql/run/mysqld.pid [client]socket=/data/mysql/run/mysql.sockdefault-character-set=utf8 [mysqld]server-id=1basedir=/usr/local/mysql-5.7.27socket=/data/mysql/run/mysql.socktmpdir=/data/mysql/tmpdatadir=/data/mysql/datadefault_authentication_plugin=mysql_native_passwordport=3306user=root#innodb_page_size=4k max_connections=2000back_log=4000performance_schema=OFFmax_prepared_stmt_count=128000#transaction_isolation=READ-COMMITTED #fileinnodb_file_per_tableinnodb_log_file_size=2048Minnodb_log_files_in_group=2innodb_open_files=10000table_open_cache_instances=64 #buffersinnodb_buffer_pool_size=32Ginnodb_buffer_pool_instances=64innodb_log_buffer_size=2048M #tunesync_binlog=1innodb_flush_log_at_trx_commit=1innodb_use_native_aio=1innodb_spin_wait_delay=780innodb_sync_spin_loops=25innodb_flush_method=O_DIRECTinnodb_io_capacity=30000innodb_io_capacity_max=40000innodb_lru_scan_depth=9000innodb_page_cleaners=16#innodb_spin_wait_pause_multiplier=25 #perf specialinnodb_flush_neighbors=0innodb_write_io_threads=24innodb_read_io_threads=16innodb_purge_threads=32 sql_mode=STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION,NO_AUTO_VALUE_ON_ZERO,STRICT_ALL_TABLES skip_log_bin#log-bin=mysql-binssl=0table_open_cache=30000max_connect_errors=2000innodb_adaptive_hash_index=1创建测试数据库# mysql -uroot -p123456> create database sysbench; 客户端已部署sysbench 0.5压测工具,修改sysbench安装路径下(./sysbench/tests/db/common.lua)的建表语句,为每张表创建366个分区,第一个红框标注中的内容为新增的部分(图中为单表创建36个分区,请注意修改为366) 使用修改后的建表文件导入数据20 * 5000000(红色字体为需要根据实际环境进行修改的配置项)# sysbench --db-driver=mysql --test=/tmp/client/sysbench-0.5/oltp.lua --oltp-test-mode=complex --mysql-host=192.168.220.62 --mysql-db=sysbench --mysql-password=123456 --max-time=7200 --max-requests=0 --mysql-user=root --mysql-table-engine=innodb --oltp-table-size=5000000 --oltp-tables-count=20 --rand-type=special --rand-spec-pct=100 --num-threads=60 prepare 数据导入成功后,启动命令进行压测# sysbench  --test=/tmp/client/sysbench-0.5/oltp.lua --db-driver=mysql --debug=off --mysql-db=sysbench --mysql-password=123456  --oltp-tables-count=20 --oltp-distinct-ranges=0 --oltp-index-updates=1 --oltp-non-index-updates=1 --oltp-order-ranges=0 --oltp-point-selects=9 --oltp-simple-ranges=0 --oltp-sum-ranges=0 --oltp_delete_inserts=1 --oltp-table-size=5000000 --num-threads=512 --max-requests=0 --max-time=1200 --oltp-auto-inc=off --mysql-engine-trx=yes --oltp-test-mod=complex  --mysql-host=192.168.220.62 --mysql-port=3306 --mysql-user=root --oltp-user-delay-min=10   --oltp-user-delay-max=100 --report-interval=1 --forced-shutdown=1 run 6、待压测进行约2分钟时在数据库端执行命令truncate一张不会被压测的表:> truncate sysbench.sbtest20;  会观察到sysbench压测tps严重下降;待truncate完成,tps性能恢复至初值  三:问题原因分析官网问题描述分析通过查看官网对该问题的描述,需要满足两个条件(buf_pool比较大和开启自适应哈希)才能导致性能下降,经过对比测试,关闭自适应哈希后,性能影响较小。 代码走读分析通过走读代码的形式了解truncate的执行步骤后,发现在遍历buf_pool之前锁住了自适应哈希,初步确定是由于该锁引起的性能下降,代码如下:    btr_search_s_lock_all();    DEBUG_SYNC_C("simulate_buffer_pool_scan");    buf_LRU_flush_or_remove_pages(id, BUF_REMOVE_ALL_NO_WRITE, 0);    btr_search_s_unlock_all();  UNIV_INLINEvoidbtr_search_s_lock_all(){    for (ulint i = 0; i < btr_ahi_parts; ++i) {        rw_lock_s_lock(btr_search_latches[i]);    }} 各个步骤执行时间分析采用minitrace统计各个步骤的执行时间如下 从统计结果可知,主要耗时在buf_LRU_flush_or_remove_pages函数,执行该函数之前锁住了自适应哈希,结束后才释放。 4. 锁的分析采用dim_STA软件统计执行truncate期间锁的等待时间,结果如下:从图可知主要在等待btr_search_latch,该锁就是自适应哈希锁。 5.总结通过以上分析,得出性能下降的原因主要是自适应哈希锁。 四:优化方案通过走读代码得知,该锁主要是保护自适应哈希数据不被修改,通过测试发现buf_LRU_flush_or_remove_pages函数并不会去修改自适应哈希数据,原因是在前面的步骤中已经清理了自适应哈希数据(在删除索引时)。通过查看代码的注释和提交记录看,作者的本意是为了阻止在执行truncate期间关闭自适应哈希数据。代码注释如下/* Lock the search latch in shared mode to prevent user    from disabling AHI during the scan */如果去修改自适应哈希数据,需要添加X锁,这将导致死锁。通过以上分析,得出解决方案是去掉自适应哈希锁,采用一个新锁来阻止执行truncate期间关闭自适应哈希功能,考虑到作者的实现方式,也会阻止执行truncate期间开启自适应哈希功能,出于这个考虑,采用了同一把新锁来阻止开启自适应哈希功能,由于找不到需要清理自适应哈希数据,所以去掉多余的步骤,不执行buf_LRU_drop_page_hash_for_fablespace函数。具体修改方案见附件 五:优化效果未做优化之前执行总耗时76秒,在51并发下,锁的等待时间接近50秒,性能从2万左右下降到500左右,性能下降非常明显。 2. 去除自适应哈希锁执行总耗时基本没变,锁的等待时间降低到25秒左右,在执行truncate时,性能从之前的500提升到1万左右,接近20倍的提升。 3.去除自适应哈希锁和多余的清理自适应哈希数据的函数执行时间从75秒缩短到46秒,性能进一步提升到1.5万左右,等待时间进一步缩短到15秒左右。 六:再次优化从以上的结果看,虽然性能得到了巨大的提升,但是仍然有25%左右的性能损失,由于导致性能下降的原因和buf_pool的大小有关,所以采用提高buf_pool的实例数来进一步提升性能。 七:再次优化效果以下是实例数为64的结果执行时间基本没变,自适应哈希锁的等待时间几乎为0,性能基本没有损失。 八:方案影响评估1. 逻辑上评估影响2. 代码影响见自适应哈希锁的影响.emmx 九:优化效果说明1. 只去掉自适应哈希锁后,锁的等待时间为什么还有25秒左右?主要原因是,在执行truncate期间锁住了buf_pool,有些业务在拿到自适应哈希锁后(btr_seatch_guess_on_hash),还需要拿到buf_pool,由于迟迟未拿到buf_pool,导致持有自适应哈希锁的时间变长。 2. 为什么增加实例数能减少自适应哈希锁的等待时间?原因和问题1相同。 附录1.8.0版本truncate实现方案通过阅读8.0的代码得知,主要是通过rename_tablespace(修改表空间路径),delete_impl(删除表),create_impl(创建表)实现truncate功能。代码如下回滚的实现:rename_tablespace()修改表空间路径,不做数据清理,create成功提交后再清理,通过log_DDL记录日志可回滚。 2.5.7.27版本truncate实现方案Truncate主要流程集中在row_truncate_table_for_mysql函数中,该函数主要分12步。    Step-1: Perform intiial sanity check to ensure table can be truncated.    This would include check for tablespace discard status, ibd file    missing, etc ....     Step-2: Start transaction (only for non-temp table as temp-table don't    modify any data on disk doesn't need transaction object).     Step-3: Validate ownership of needed locks (Exclusive lock).    Ownership will also ensure there is no active SQL queries, INSERT,    SELECT, .....     Step-4: Stop all the background process associated with table.     Step-5: There are few foreign key related constraint under which    we can't truncate table (due to referential integrity unless it is    turned off). Ensure this condition is satisfied.     Step-6: Truncate operation can be rolled back in case of error    till some point. Associate rollback segment to record undo log.     Step-7: Generate new table-id.    Why we need new table-id ?    Purge and rollback case: we assign a new table id for the table.    Since purge and rollback look for the table based on the table id,    they see the table as 'dropped' and discard their operations.     Step-8: Log information about tablespace which includes    table and index information. If there is a crash in the next step    then during recovery we will attempt to fixup the operation.     Step-9: Drop all indexes (this include freeing of the pages    associated with them).     Step-10: Re-create new indexes.     Step-11: Update new table-id to in-memory cache (dictionary),    on-disk (INNODB_SYS_TABLES). INNODB_SYS_INDEXES also needs to    be updated to reflect updated root-page-no of new index created    and updated table-id.     Step-12: Cleanup Stage. Reset auto-inc value to 1.    Release all the locks.    Commit the transaction. Update trx operation state. 
  • [数据库] MySQL-test 框架
    MySQL test 框架参考https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_TESTING_TOOLS.html测试框架程序文件mysql-test-run.pl测试主程序 调用mysqltest测试单个用例(单个测试文件)mysqltest测试单个用例,被mysql-test-run.pl调用mysql_client_test用来测试无法被mysqltest测试的MySQL client APImysql-stress-test.pl用于MySQL压力测试unit-testing facility 用于创建测试存储引擎或插件的单独的单元测试测试suite程序文件所在目录mysqltest源码cc在client目录下,编译结果在bin目录下mysql_client_test源码cc在testclients目录下,编译结果在bin目录下其他测试程序 源码在mysql-test目录下,编译结果在install目录下的mysql-test目录install/mysql-test目录结构```bash-rw-r--r--.  asan.suppdrwxr-xr-x.  collections         # 集成与发布测试时使用,保留在源码仓中以供参考drwxr-xr-x.  extradrwxr-xr-x.  include             # 主要版本default_xxx.cnf文件及一些将被test文件包含的.inc文件drwxr-xr-x.  lib                 # 保存了一些.pm .t .pl文件,将作为mysql-test-run.pl的模块被调用-rw-r--r--.  lsan.supplrwxrwxrwx.  mtr -> ./mysql-test-run.pl              # 别名或副本-rwxr-xr-x.  mysql-stress-test.pl                    # 压力测试lrwxrwxrwx.  mysql-test-run -> ./mysql-test-run.pl   # 别名或副本-rwxr-xr-x.  mysql-test-run.pl                       # 用来一次测试drwxr-xr-x.  r                   # 存放.result期望结果文件、.reject(与.result不一致的)实际结果文件-rw-r--r--.  README-rw-r--r--.  README.gcov-rw-r--r--.  README.stressdrwxr-xr-x.  std_data            # 包含一些测试使用的数据文件drwxr-xr-x.  suite               # 每个子目录代表一个以文件件命名的test suitedrwxr-xr-x.  t                   # 存放测试输入文件 .cnf .inc .opt .test等文件-rw-r--r--.  valgrind.suppdrwxr-xr-x.  var                 # 存放各种测试结果信息```t目录t包含了测试case的输入文件,对于一个用例ABC可能有文件ABC.cnf指定测试case的附加配置信息ABC-client.opt提供客户端的配置ABC-master.opt即使没有涉及主从复制,也加master,如果当前运行的server的配置和-master.opt的不一样,mysql-test-run.pl就会重启server;mysql-test-run.pl也会按照opt文件的配置重启server。每个bootstrap变量必须作为--initialize选项的参数,mysql-test-run.pl才能在服务器初始化的时候识别出需要使用的变量ABC-slave.opt有主从复制是才需要ABC.testABC.resultABC.combinations为每次测试case运行提供选项段ABC-master.sh在main server启动前将被执行,win不支持,将来可能被其他机制替换ABC-slave.sh在slave server启动前将被执行,win不支持,将来可能被其他机制替换suite.opt为所有该suite内的test case提供配置,如果一个test运行多个server,则suite.opt对所有这些server都有效。该文件中的选项会被-master.opt、-slave.opt中的同选项覆盖disabled.def用来配置将被延期或禁止运行的test case,如果由于server有bug致使一些test会失败,想忽略这些test,不被mysql-test-run.pl执行,可以将这些test列到这个文件中cnf文件可以包含基础或其他配置文件!include include/default_my.cnf [mysqld.1]                      # 可以使用.1/.2等组后缀名区分不同server组,每个server启动的时候都默认带有组后缀名(defaults-group-suffix)Options for server mysqld.1 [mysqld.2]Options for server mysqld.2 [mysqltest]                     # 测试客户端的配置选项ps-protocol ......... [ENV]                           # 指定测试case的环境变量SERVER_MYPORT_1= @mysqld.1.port # 定义一个SERVER_MYPORT_1环境变量,值为上段mysqld.1中的port的值SERVER_MYPORT_2= @mysqld.2.portr目录可能包含的文件ABC.resultABC.test文件的期望输出内容ABC.reject如果test case是由于输出不一致而失败的(非其他原因失败),则.reject文件中包含test case的实际输出如果--check-testcases选项打开,若test文件没有对应result文件时,mtr将对其标记为失败。--check-testcases作用检查测试用例是否有副作用。 这是通过在每个测试用例之前和之后检查系统状态来完成的。 如果有任何差异,则测试用例因此被标记为失败。类似地,当启用 --check-testcases 选项时,MTR 会对丢失的 .result 文件进行额外检查,并且没有相应 .result 文件的测试用例被标记为失败。默认情况下启用此检查。 要禁用它,请使用 --nocheck-testcases 选项。var目录用来存放各种测试运行中生成的结果文件:log文件、temp文件、trace文件、Unix socket文件等。这个目录不能被同时跑的测试所共享。suite目录该目录下每个子文件夹代表一个与文件夹同名的test suite。 每个test suite可能包含如下部分t目录r目录include目录一个combinations格式的文件,为每次测试运行提供配置段(Controlling the Binary Log Format Used for an Entire Test Run)一个cnf格式的文件,为本suite中的所有测试提供配置项,同配置项内容会被test_name.cnf文件的所覆盖。collections目录此目录包含我们在集成和发布测试期间运行的测试运行的集合。这些文件在此上下文之外没有直接用处,但需要成为源码仓的一部分并包含在内以供参考。每个文件包含零行或多行,每行都调用一次 mysql-test-run.pl。这些调用是这样编写的假设perl在环境搜索路径中原则上任何集合都可以作为shell脚本或批处理文件运行mysql-test目录是当前工作目录。每行格式例如 perl mysql-test-run.pl --force --timer --big-test --testcase-timeout=90 --parallel=auto --experimental=collections/default.experimental --comment=normal-big --vardir=var-normal-big --report-features --skip-test-list=collections/disabled-daily.list --unit-tests-reportunittest目录单元测试相关目录,相关于存储引擎和插件的附加文件可能存在于storage或plugin目录的子目录下。在顶层Makefile中有多个targets可用于运行测试集。make test只运行单元测试(?),其他测试集见Makefile文件。 一个“test case”是单个文件,case中可能包含多个测试命令,任意一个测试命令没有产生预期的结果都认为整个测试用例失败(预期结果包括测试某种预期的错误,例如语法错误)。test case的输出内容(test result, 和.result文件进行diff)包括输入的SQL语句及其输出信息mysqltest命令(例如echo、exec)的输出结果,而命令本身不输出到结果。disable_query_log和enable_query_log命令控制是否logging输出SQL语句(.result?) disable_result_log和enable_result_log命令控制是否logging输出SQL语句的结果包括warning、error信息(.result?)mysqltest默认从其标准输入读入test case,也可以使用--test-file或-X选项显式地给定一个test case文件名。mysqltest默认向其标准输出写入test case的结果,也可以使用--result-file或-R选项来显式地指定result文件的位置。 这个配置项和--record选项共同确定mysqltest如何处理一个test case的实际和预期测试结果。如果一个test没有输出result,mysqltest会带着error信息退出,除非--result-file指定的文件名为空如果--result-file没有给出,mysqltest将发送结果到标准输出如果有--result-file但没有--record选项mysqltest从指定的文件中读取期望的result文件,并且和期望的result结果做比较。如果结果不匹配,mysqltest就将实际结果写到log目录下.reject文件中,并error退出,只要有可用的diff工具,就会再输出实际与预期的diff结果。如果--result-file和--record都给出了,则mysqltest将用实际测试结果更新到给出的文件中,该文件不需要预先存在(最开始的result文件自动生成)。mysqltest程序本身对t/r目录一无所知,这些目录下的文件,约定由mysql-test-run.pl使用,由该pl文件为每个test case以适当的参数调用mysqltest,告诉mysqltest从哪里读取输入和向哪里输出。需要C++运行时库mysqltest和mysql_client_test程序是用C++编写的,可以在任何可以编译MySQL本身的系统上使用,或者可以使用二进制MySQL发行版。需要perl测试框架的其他部分,例如 mysql-test-run.pl 是 Perl 脚本,应该在安装了 Perl 的系统上运行。需要diffmysqltest使用diff程序来比较预期和实际测试结果。 如果未找到diff,mysqltest会写入错误消息并转储 .result 和 .reject 文件的全部内容,以便您可以尝试确定测试未成功的原因。 如果您的系统没有diff,您可以从以下站点之一获取它: http://www.gnu.org/software/diffutils/diffutils.html http://gnuwin32.sourceforge.net/packages/diffutils.htm目录不能带空格如果从完整路径包含空格字符的目录中启动,mysql-test-run.pl 将无法正常运行,因为这将会使在所有引用这个路径的不同上下文中正确处理它变得很复杂。参考 https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_MYSQL_TEST_RUN_PL.html尽可能多收集错误信息,再上报bughttps://dev.mysql.com/doc/refman/8.0/en/bug-reports.html.确保包含了mysql-test-run.pl的输出、var/log中所有的.reject文件以及diff报告检查单独跑这个用例是否失败cd mysql-test./mysql-test-run.pl test_name如果还失败,再继续检查是否自己编译MySQL使用了--with-debug选项且运行mysql-test-run.pl是否使用了--debug选项。如果这样还失败,则连带var/tmp/master.trace文件一起上报(顺带包含系统描述、mysqld版本、如何编译该mysqld文件)。运行mysql-test-run.pl带--force选项,查看是否还有其他test case失败。Result length mismatch或者Result content mismatch,就表示可能有bug或该mysql版本在某些情况下产生的结果略有不同。如果一个test case完全失败,应该检查var/log目录中的logs文件中的错误信息。如果自己编译的debug版本MySQL,出现test case失败的情况,可以运行mysql-test-run.pl加--gdb和--debug选项来调试失败原因。在CMake时可以使用-DWITH_DEBUG来指定编译debug版本的MySQL 
  • [数据库] MySQL-test programs
    MySQL test programs  参考   https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_MYSQLTEST_LANGUAGE_REFERENCE.htmlhttps://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_MYSQLTEST_PROGRAMS.html mysql-test-run.plperl脚本文件,mtr测试的主程序,负责server环境的初始化、server的启动、调用mysqltest程序进行test case的测试mysqltest二进制可执行文件,被mysql-test-run.pl调用,用于test case的执行和输出结果比较mysql_client_test二进制可执行文件,用于无法被mysqltest测试的MySQL client API的测试mysql-stress-test.plperl脚本文件,用于MySQL server压力测试mysqltest功能   可以发送SQL语句到MySQL server被执行可以执行外部shell命令(?)可以测试SQL语句或shell命令的结果是否符合预期可以连接到一个或多个独立的mysqld服务器,并在连接之间切换一堆参数,略mysql-test-run.pl  test cases指定cd mysql-test ## 每个test_name参数命名一个test case。 测试名称对应的test case文件是t/test_name.test。 没有指定test_name参数时,执行t目录下所有.test文件。mysql-test-run.pl [options] [test_name] ... ## 如果没有给定后缀名,则假定后缀名为.test,如果带前导目录,则忽略任何前导目录(总之别乱用,用test_name就可以了)mysql-test-run.pl mytestmysql-test-run.pl mytest.testmysql-test-run.pl t/mytest.test ## suite名称作为test_name的一部分,对应test case文件为suite/suite_name/t/test_name.test,mysql-test/t目录下的test cases的suite_name为隐式的"main".mysql-test-run.pl [suite_name.]test_name[.suffix] ## 如果默认suite列表中包含多个同名的test case,则不指定suite_name会执行默认suite列表下的所有test_name的test case (默认suite列表在哪里配置?)mysql-test-run.pl test_name  测试环境初始化运行测试前为了初始化设置,mysql-test-run.pl需要调用mysqld带上--initialize参数和--skip-grant-tables(不启用授权表,使用后可任意用户名密码登录)选项。如果编译MySQL时用了-DDISABLE_GRANT_OPTIONS,则 --initialize、 --skip-grant-tables、--init-file参数将不可用。则用MYSQLD_BOOTSTRAP环境变量来指定可用这些选项的mysqld的完整路径。这个只用来做初始化,不在测试运行时使用。(主要就是用来初始化数据库文件?)如果 --init-file 被禁用,init_file 测试将失败。 在这种情况下,这是预期的失败。windows平台windows平台上运行mysql-test-run.pl,需要安装Cygwin和Perl运行库,并需要安装脚本依赖的模块。运行脚本如下cd mysql-testexport MTR_VS_CONFIG=debug  ## 或者使用--vs-config选项./mysqltest-run.pl --force --timer./mysqltest-run.pl --force --timer --ps-protocol环境变量mysql-test-run.pl中使用的环境变量,一些是mysql-test-run.pl外部设置,用于mysql-test-run.pl,一些是由mysql-test-run.pl内部设置,在测试运行时使用。环境变量名作用MTR_BUILD_THREAD如果设置,则用于定义server的端口号(需要间接计算得到端口号)MTR_MAX_PARALLEL当--parallel=auto时,定义可并发的最大线程数MTR_MEM如果设置(不管设置为什么内容),将在内存上用tmpfs或ramdisk方式运行测试,提升mtr测试的速度(测试失败信息能否保留出来?)MTR_NAME_TIMEOUT对应于命令行选项--name-timeout,NAME可替换为TESTCASE、SUITE(单位为分钟),还可替换为START、SHUTDOWN、CTEST(单位为妙)。 MTR_CTEST_TIMEOUT用于ctest单元测试MTR_PARALLEL如果定义了,则定义并行执行的线程数,同--parallel选项MTR_PORT_BASE如果定义了,直接定义用于server的端口号范围MTR_RECORD如果MTR是带--record选项运行的,则该环境变量值为1,否则为0,用于用例执行时判断是否需要更新.test文件MTR_UNIQUE_IDS_DIR获取空闲端口的方法是通过从一个唯一ID列表中获取,当使用chroot运行多个mtr实例时,每个mtr使用自己的唯一ID目录,将导致可能为多个mtr分配相同端口,导致端口冲突,使用该环境变量,使所有mtr共用一个唯一ID目录,避免端口冲突MYSQL_BIN_PATHmysqld等文件所在路径MYSQL_CLIENT_BIN_PATHmysql client程序所在路径MYSQL_CONFIG_EDITORmysql_config_editor程序所在目录MYSQL_TESTmysqltest程序所在路径MYSQL_TEST_DIRmysql-test所在路径的全路径名MYSQL_TEST_LOGIN_FILEmysql_config_editor使用的login文件的路径名,若没有配置,则默认使用$HOME/.mylogin.cnf或者%APPDATA%\MySQL\.mylogin.cnf(windows)MYSQL_TMP_DIR运行测试时的temp目录路径MYSQLDmysqld程序文件的完整路径名MYSQLD_BOOTSTRAP具有所有选项可用的mysql的程序文件的完整路径名,该mysqld只用于环境初始化MYSQLD_BOOTSTRAP_CMD用于本次测试初始化数据库设置的完整命令行MYSQLD_CMD用于启动测试中使用的服务器的命令行,具有最少的必需参数集。MYSQLTEST_VARDIR测试中var目录的路径NUMBER_OF_CPUS定义cpu处理器的数量TSAN_OPTIONS包含 ThreadSanitizer 抑制的文件的路径名。MTR_PORT_BASE是MTR_BUILD_THREAD更逻辑直接的替代者,MTR_PORT_BASE配置端口的逻辑是对该配置值向下取整为10的倍数即目标端口号,MTR_BUILD_THREAD的逻辑是配置值 * 10 + 10000为目标端口号测试有时依赖于定义的某些环境变量。例如某些测试假定MYSQL_TEST已定义,以便mysqltest可以通过exec $MYSQL_TEST调用自身(如果假定定义了环境变量,实际没有定义,是否执行报错?)其他测试可能会引用其他一些环境变量,例如用来定位读和写的文件位置,如需要创建文件的test通常将文件创建为$MYSQL_TMP_DIR/file_name。$MYSQLD_CMD包含的是--mysqld选项带来的对所有测试的server都有效的配置项,但不包含为当前测试特定配置的server选项(这个环境变量类似一个状态变量而不是一个配置变量?)mysql-test-run.pl命令行参数mysql-test-run.pl支持如下命令行参数,"-"参数用来告知mysql-test-run.pl不再将后续的参数当做配置选项来处理。--skip-test=prefix or regex跳过指定前缀或(perl)正则匹配的test_name的test cases--skip-test-list=file--do-test=prefix or regex只执行指定前缀或(perl)正则匹配的test_name的test cases,例如--do-test=main.testa匹配到的是main suite下的所有testa开头的test cases,而--do-test=main.*testa匹配到的是所有test_name包含main后0个或n个字符后接testa结尾格式的test cases,不要求main开头,例如xmainytesta,.*表示0个或n个任意字符--do-test-list=file用文件指定要测试的case,文件中每行一个test_name,注释使用#开头--do-suite=prefix or regex类似--do-test--big-test用于允许标记了"big"的test cases执行。所谓标记了"big"即包含了--source include/big_test.inc的test case,这些test case只有在--big_test给出和BIG_TEST设置为1时才允许执行。 big test通常用于需要很长时间运行或使用大量资源的测试,因此它们不适合作为正常测试套件运行的一部分运行。 --big-test和--only-big-tests同时配置时,--only-big-tests将被忽略--boot-dbx通过dbx调试器运行用于引导(bootstrap)数据库的mysqld--boot-ddd通过ddd调试器运行用于引导(bootstrap)数据库的mysqld--boot-gdb通过gdb调试器运行用于引导(bootstrap)数据库的mysqld--manual-boot-gdb和--boot-gdb类似,还允许使用远程的调试器--manual-dbx使用已在dbx调试器中启动的mysql server做test case测试--manual-ddd使用已在ddd调试器中启动的mysql server做test case测试--manual-debug使用已在调试器(不管啥调试器?)中启动的mysql server做test case测试--manual-gdb使用已在gdb调试器中启动的mysql server做test case测试,可以用来调试testcase的失败原因--build-thread=number指定一个数字来计算端口号。 公式为10 * build_thread + 10000,可以设置为auto,而不是数字,也是默认值,这样mysql-test-run.pl就会分配一个本机唯一的数字,该配置值(数字或auto)也可以使用 MTR_BUILD_THREAD 环境变量设置。保留此选项是为了向后兼容。 推荐使用更合乎逻辑的 --port-base。--callgrind指示命令格式使用callgrind。(?)--charset-for-testdb=charset_name指定数据库的默认字符集,默认值为latin1--check-testcases检查test cases是否有副作用,通过每个test case测试前后检查系统状态,如果系统状态不一致,则将test case标记为失败--clean-vardirClean up the var directory with logs and test results etc. after the test run, but only if there were no test failures. This option only has effect if also running with option --mem. The intent is to alleviate the problem of using up memory for test results, in cases where many different test runs are being done on the same host.(?未理解)--client-bindir=pathclient(mysqltest?) 二进制文件所在路径--client-dbx在dbx调试器中启动mysqltest--client-ddd在ddd调试器中启动mysqltest--client-gdb在gdb调试器中启动mysqltest--client-debugger=debugger_name在指定名称debugger_name的调试器中启动mysqltest--client-libdir=pathclient lib库所在目录(?)--colored-diff输出diff不同处着色,mysqltest会调用diff命令的--color='always'参数--combination=value--comment=str--compress--cursor-protocol--ddd--debug--debugger=debugger--debug-server运行 mysqld.debug(如果可用)而不是 mysqld 作为服务器。 如果确实找到了mysqld.debug,它会在它通常所在目录下的子目录debug 中搜索插件库。 此选项不会打开跟踪输出,并且与调试选项无关。--debug-sync-timeout=seconds--default-myisam--defaults-file=file_name--enable-disabled--explain-protocol--extern option=value--fast--force当测试用例失败,若加了--force,将执行继续,否则mysql-test-run.pl会退出而测试结束--force-restart加了该参数,每次测试用例运行前总是重启mysql服务。对于一些测试用例很有用,例如测试内存泄漏,泄漏范围保证只在一个用例范围内--gcov--gdb在 gdb 调试器中启动 mysqld(?)--gprof--include-ndbcluster, --include-ndb--initialize=value--json-explain-protocol--mark-progress--max-connections=num--max-save-core=N--max-save-datadir=N--max-test-fail=N--mem--mysqld=value--mysqld-env=variable=value--mysqltest=options--ndb-connectstring=str--no-skip--nocheck-testcases--noreorder--notimer--nounit-tests--nowarnings--only-big-tests--parallel={N/auto}--port-base=P--print-testcases--ps-protocol--quiet--record--reorder--repeat=N--report-features--report-times--report-unstable-tests--retry=N--retry-failure=N--sanitize--shutdown-timeout=seconds--skip-combinations--skip-ndbcluster, --skip-ndb--skip-ndbcluster-slave, --skip-ndb-slave--skip-rpl--skip-*--sp-protocol--start--start-and-exit--start-dirty--start-from=test_name--strace-client--strace-server--stress=stress options--suite(s)={suite_name/suite_list/suite_set}--suite-timeout=minutes--summary-report=file_name--test-progress[={0/1}]--testcase-timeout=minutes--timediff--timer--timestamp--tmpdir=path--unit-tests--unit-tests-report--user=user_name--user-args--valgrind--valgrind-clients--valgrind-mysqld--valgrind-mysqltest--valgrind-option=str--valgrind-path=path--vardir=path--verbose--verbose-restart--view-protocol--vs-config=config_val--wait-all--warnings--with-ndbcluster-only, --with-ndb-only--xml-report=file_name测试举例cd mysqldircd build_releasecd install ./mtr \--report-unstable-tests \--force \--timestamp \--timer \--max-test-fail=0 \--suite-timeout=9000 \--testcase-timeout=300 \--parallel=8 \--retry=0 \--xml-report=./mtr-report_r.xml \--big-test
  • [知识分享] 【数据库系列】15张图呈现数据库事务背后的并发原理
    本文分享自华为云社区《[将数据库9种锁、3种读、4种隔离级别一次性串联起来,用15张图呈现背后数据库事务背后的并发原理](https://bbs.huaweicloud.com/blogs/335953?utm_source=csdn&utm_medium=bbs-ex&utm_campaign=other&utm_content=content)》,作者: breakDawn。 前段时间开发时,正好遇到了2个进程同时更新一行记录时引发的bug,虽然问题最终解决了,但自己对背后的运行逻辑仍旧一头雾水。事后尝试简单翻了下各种博客资料,还有《高性能mysql》那本书时,发现大部分是将一堆八股文概念堆砌在一起,很少完整串联过这堆概念。 于是我重新完整学习了这些概念和底层原理, 通过一个转账问题的场景,将这些概念全部关联起来。 将下面这些数据库的概念单独拿出来时,相信很多人都有了解或者记忆过,但是将这些概念全部串联在一起时,可能就会很混乱。 我这里举个例子: - 排他锁、共享锁 - 行锁、表锁、意向锁、间隙锁、next-key锁 - 悲观锁、乐观锁 - 两阶段锁协议 - LCBB锁并发控制协议、MVCC多版本控制协议 - 脏读、不可重复读、幻读 - RU\RC\RR\SE隔离级别 然后自己问自己一个问题: 1. 这一堆锁的关联关系究竟是什么? 2. 各隔离级别究竟是怎么用各种锁+MVCC来解决事务读问题的? 首先,我们完全不考虑数据库引擎、隔离级别设置之类的,就当作你用一个超简陋的儿科级别数据库来存放和更新数据。 假设你的商城服务正好在**同时执行**如下的2种事情 - 张三给穷光蛋李四转账100元。 - 李四尝试下单购买100元的衣服 李四在最开始余额只有0元钱。 注意因为是同时执行,在没有做任何保护的情况下,就可能会出现下图这样的情况 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/11/1646965083803273652.png) 可以看到李四明明没有钱,却扣费了,变成了很奇怪的-100元。 Q:那这个有问题的读过程叫什么? A:这个过程就叫做**脏读**。 即更新回退的时,另一个事务读到了脏数据,判断失误,导致做了错误的处理。 **根本原因是2个事务都是先查后扣,却没有提前保护的形式** Q:在不修改数据库隔离级别的情况下, 我们可以如何用sql语句手动解决这个脏读? A:那很显然就是加锁对事务过程做提前保护, 不让B去判断和扣费。 sql语句里有个 ”for update“ 语法, 会手动锁住李四那一行,在调用commit后释放 具体见下面绿色的标注部分: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/11/1646965115795851323.png) Q:刚才看到”锁住李四这一行“, 那么这个就叫**行级锁**。 什么情况下会变成锁住整个表? A:name ='李四’这句话, 如果name是索引列的话,就会加行锁 如果不是索引列, 就会变成表锁。 换言之, **行锁的本质是在索引节点上加锁** 如果无法在索引节点上加锁,那就会直接变成整张表的锁,代价就会很大。 另外表锁也可以单独用lock table的语法手动加锁 Q:如果一个事务A申请了行锁,锁住某一行, 另一个事务B申请了表锁,那B会被阻塞吗? A:B事务既然申请表锁,说明可能会用到A中的每一行。 B申请的流程可以是下面这样: 1. 判断表是否已被其他事务用表锁锁表 2. 判断表中的每一行是否已被行锁锁住。 但2这一步也太耗时了。 因此A申请行锁前,会优先申请一个意向锁,再申请行锁。 然后B申请时,第2步改成判断意向锁即可,有意向锁就阻塞。 简单点说, 意向锁就是行锁操作用来阻塞表锁用的。 但行锁和行锁之间不会互相阻塞,除非行有冲突。 刚才看到的for update会限制其他并行事务的所有读写操作,而且是2个事务上都加了”for update“。 那么这个锁就叫做”排他锁“, 属于非常强势的锁, 相当于其他读写操作马上全部拦住了。 这里使用排他锁来解决脏读的原因是因为后面有**查询余额+扣余额**的代码,写这段代码的人必须做提前保护,**以避免自己读到一个可能被修改的数据,导致判断和修改失误。** 和排他锁对应的是“共享锁”,也就是熟知的读写锁。 可以让多个事务同时读,但是不允许修改 。 手动加共享锁的方式:把for update改成 lock in share mode即可 Q:那么什么时候使用共享锁比排他锁要好呢? A:可以看下面的例子: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/11/1646965182989406118.png) 可以看到没有查自身+更新自身的操作, 仅仅是查+更新其他表,表之间也互不关联,对余额的实时性也不是要求太高。 - 如果都加排他锁,各种select操作就会很慢。 - 但如果不加共享锁, T6这边删除时,就可能产生冗余数据,所以还是得加锁。 Q:那我加的共享锁(S锁)和排他锁(X)什么时候释放呢?是每次执行完update马上释放吗? A:这里就涉及了“两阶段锁”协议。 - 加锁阶段:在该阶段可以进行加锁操作。在对任何数据进行读操作之前要申请并获得S锁(共享锁,其它事务可以继续加共享锁,但不能加排它锁),在进行写操作之前要申请并获得X锁(排它锁,其它事务不能再获得任何锁)。加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。 - 解锁阶段:当事务释放了一个封锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。 说人话, 就是在事务中需要加锁时再加锁, 直到commit完一次性解锁。 为什么要两阶段锁,看到的一句话是 **若并发执行的所有事务均遵守两段锁协议,则对这些事务的任何并发调度策略都是可串行化的。** Q:两阶段锁协议可以避免死锁吗? A:不能避免,但是可以通过死锁检测算法进行事务解除。 重新回到张三李四转账+下单的场景上来。 for update这种锁,其实也是一种“悲观锁” ,加锁解锁比较耗时, 默认经常发生竞争。 但如果我的转账和下单过程要求非常快,每次只有几毫秒,那加悲观锁成本就太大了 这时候就可以手动使用乐观锁, 需要你自己在余额表里增加version列,增加后如下所示: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/11/1646965227615667454.png) 这样就不需要特地加锁了,每次循环判断即可,前提是冲突发生概率比较低,阻塞时间比较短。 刚才一个小小的脏读,就已经解决了下面3个问题 - 排他锁和共享锁的区别:前者是拒绝所有读写 , 后者是允许并发读拒绝写 - 行锁和表锁的区别: 前者是对单行加锁 , 后者是对整表加锁, 区别是 是否涉及索引 - 悲观锁和乐观锁的区别: 前者主动用数据库自带的锁, 后者自己添加version版本号 外加一个两阶段锁协议 继续回到脏读问题, 前面我们学习的所有概念,都是和数据库自身隔离级别无关,使用数据库的锁语法或者version版本号来避免。 但数据库发展这么强大,怎么可能需要我们频繁自己写这种复杂逻辑,于是数据库诞生了隔离级别设置。 前面会发生脏读的隔离级别, 叫做RU(read uncommited) 即RU级别时, 我可以在别的事务没完全commit好时就读到数据。 Q:先来个小问题,RU级别没有任何锁,对吗? A:错误, RU级别做update等增删改操作时,仍然会默认在事务更新操作中增加排他锁,避免update冲突。 切记脏读的发生原因,是查询+更新+回滚时没加锁导致其他查询操作出现失误判断。 即查询这块可能读到没提交的数据,导致错误,而不是更新的并发问题。 Q:当我们的数据库被设置成RC级别(Read commited)时, 可以解决脏读, 那么背后是怎么解决的呢? A:业界有两种方式 LBCC基于锁的并发控制(Lock-Based Concurrency Control)) MVCC基于多版本的并发控制协议(Multi-Version Concurrency Control) LBCC其实就是类似前面手动用悲观锁的方式, 事务操作中查询时默认试图加锁,因此就可能被update的排他锁阻塞住,避免了脏读。 但代价就是效率很低。很多场景下,select的次数是远大于update的。 所以InnoDb 基于乐观锁的概念, 想了一个MVCC,自己在事务的背后实现了一套类似乐观锁的机制来处理这种情况。 确保了尽可能不在读操作上加锁, 排他锁只对更新操作生效。 Q:MVCC究竟是怎么做的呢? A:简单来说,就是默认给每个数据行加了一个版本号列TRX_ID和回滚版本链ROLL_BT,具体可以看《高性能mysql》书里的这段描述: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/11/1646965273536343886.png) 简而言之 - 查的时候,只查当前事务之前的记录,或者回滚版本比当前大的已删记录。 - 增的时候,加新版本的记录 - 删的时候,把老记录标记上回滚版本 - 改的时候,本质上是加新记录, 同时把老记录标上回滚版本 Q:MVCC机制下, 什么是快照读,什么是当前读? A: - 快照读:对于select读操作,统一默认不加锁,使用历史版本数据。 - 当前读:对于insert、update、delete操作,仍然需要加X锁,因为涉及了数据变更,必须使用最新数据进行修改 Q:那么回到刚才的脏读问题, MVCC究竟是怎么在读不加锁的情况下, 解决脏读的? A:首先,每次select都不用任何锁, 每次都是快照读,不会阻塞,因此会变成下面这样: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/11/1646965300885343840.png) 总结这个图,就是 1. 每次读时,会生成一个readView,用来记录当前还没提交的事务版本号。 2. 根据自己事务的版本号version,去寻找小于自己当前版本且不在readView集合中的记录。 这样的话就保证了读的数据必须是已经完成提交的,是不是很简单? Q:如果事务B中不做余额判断,支持直接赊账+扣费, 那是不是会导致先扣费,然后回滚成0这样的情况? A:不会。 上面提过, MVCC中更新操作都是“当前读”,仍然需要**加X锁**, 且因为涉及了数据变更,必须使用**最新数据版本**进行修改 换言之, update等操作, 还是会加锁,且用最新版本更新,避免了脏更新的问题,如下: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/11/1646965331415540952.png) Q:上面这个过程有什么隐患 A:如果1个事务中连续读2次余额,可能有“不可重复读”的风险,即前后读的数据发生了不一致 如下所示 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/11/1646965341371513137.png) 因此RC隔离级别无法解决 “不可重复读的问题” Q:RR(可重复读,Repeat Read)的隔离级别又是怎么解决上面这个问题的? A:本质上就是readView生成时的区别 上面RC不可重复读的图中可以看到,每次读时,都取了最新的readView。 这可能导致事务A提交后, 事务B观察到的readView集合发生了变化。 因此RR机制改变了readView的生成方式, 每次读时只使用事务B最开始拿到的那个readView,这样永远就只取老的数据了。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/11/1646965421963113664.png) Q:那读问题中的幻读又是什么? A:刚才的”不可重复读“,是一个事务中查询2次结果,**发现值对不上**。 而”幻读“,是指一个事务中查询2批结果,发现这2批**数量对不上**,就好象发生了幻觉。 就像下图所示展示: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/11/1646965434365390810.png) Q: RR隔离级别中的MVCC机制可以解决上面的问题吗? A: 可以解决。 通过查询的快照读,能够保证只查询到同一批数据。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/11/1646965456631604257.png) Q: 那如果像下面这样, 事务A连续做两次更新呢,单纯靠MVCC能避免更新操作的幻读么? A: 如果**只依靠MVCC**,那就无法避免了, 因为update操作是”当前读“,每次取最新版本做更新, 这会导致update中的读操作出现幻读,前后更新的记录数量不一样了。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/11/1646965475984614651.png) Q: 那数据库怎么处理这种2次updete中间做insert的幻读情况呢? A: 之前有了解到, update过程仍然会加锁, RR级别会启用一个叫”间隙锁“(Gap锁)的玩意,专门来防这样情况。 即调用 update xxx where name ='李四’时, 不仅仅在李四的行上加锁, 更会在中间所有行的间隙、左右边界的两边,加上一个gap间隙锁,就像下面这个图一样: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/11/1646965487850596599.png) 可以看到,订单D的插入过程被update过程的间隙锁拦住了,于是无法插入,置到事务结束才会释放。 因此事务中两次update之间的幻读是可以避免的,也能。 Q: 那行锁、间隙锁、next-key锁是什么区别? A: 行锁就是单个行(单个索引节点)加锁 间隙锁就是在行(索引节点之间)加锁 next-key就是“行锁+间隙锁”,一起使用。 Q: 如果name这个字段不是索引,而是普通字段,那间隙锁会怎么加? A: 那就会给整个表的所有间隙都加上锁! 因为数据库无法确认到底是哪个范围,所以干脆全加上。 这就会导致整表锁住,性能很差。 Q: 那是不是只要name是索引,就不会给整个表全加间隙锁了? A: 不对, 如果where条件写的有问题,不符合最左匹配原则,那也会导致索引失效, 以至于给整个表加锁。 Q: 刚才看到说RR可以解决2次select之间的幻读, 也能解决2次update之间的幻读, 那为什么很多资料里,仍然说RR不能解决幻读? A: 这个问题我也是翻了好多资料, 终于找到了一个合理的解释。 看下面这个场景: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/11/1646965514009954157.png) 发现什么区别没, 事务B的insert操作,发生在了事务A的update之前。因此事务B的insert操作没有被间隙锁阻塞。 而update用的是当前读, 于是更新的数量和 最初select的数量匹配不上了。 Mysql官方给出的幻读解释是:只要在一个事务中,第二次select多出了row就算幻读,所以这个场景下,算出现幻读了。 这也就是下面这个图的来源: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/11/1646965527762271703.png) Q: 那串行化serializable隔离级别,为什么就能避免幻读了? A: Se级别时,会从MVCC并发控制退化为基于锁的并发控制(LCBB)。 不区别快照读和当前读 所有的读操作都是当前读,读加读锁(S锁),写加写锁(X锁)。在该隔离级别下,读写冲突,因此并发性能急剧下降,在MySQL/InnoDB中不建议使用。 这就是我们文章最开头手动加锁的那个过程了。
  • [数据库] MySQL-InnoDB索引磁盘结构
    File  segment:属于一个索引的数据页的集合索引申请空间的单位:extent,空间上连续的 64 个数据页,注意,当 file segment 刚被创建时,会首先被分配一些 零散的数据页(32 个)来使用,当这些数据页不够用时,才以 extent 为单位申请新的空间。INODE page:每个 tablespace 的第三个页,INODE page 里包含 85 个 INODE entry INODE entry :对应于一个 file segment。用来记录分配给该file segment 的所有 extent 及使用情况 INODE entry 内容FSEG_ID:file segment id,若值为 0 表示该 INODE entry 未被使用FSEG_MAGIC_N:magic numberFSEG_FREE:分配给该 file segment ,并且完全没有被使用的 extent 链表FSEG_FULL:分配给当前file segment,且数据页被用尽的 extent 链表FSEG_NOT_FULL:FSEG_FREE 链表上 extent 中数据页被部分使用后,移动到FSEG_NOT_FULL 链表;FSEG_NOT_FULL 链表中的 extent 中数据页用尽后,移动到 FSEG_FULL 链表。反之也成立FSEG_FRAG_ARR:属于该file segment 的独立的数据页数组(32页)FSEG_NOT_FULL_N_USED:FSEG_NOT_FULL链表上被使用的数据页数量 extent descripter page(简称 XDES):每 256 个 extent 为一组,用来记录包含其在内的 256 个 extent,在文件中每 256M 有一个 XDES 页其中整个 tablespace 的第一个 XDES 有点特殊,由 fsp header 和 256 个 entry 组成(XDES_ARR_OFFSET)。其余 XDES 只包含 256 个 entry 。在fsp header:描述文件空间使用情况每个域的意义是:FSP_SPACE_ID:该文件对应的 tablespace idFSP_NOT_USED:保留字节,当前未使用FSP_SIZE:当前表空间数据页总数,扩展文件时需要更新该值FSP_FREE_LIMIT:当前尚未初始化的最小 page no。从该页往后的都尚未加入到表空间的 FSP_FREE 上FSP_SPACE_FLAGS:当前表空间的 flag 信息,见下文FSP_SEG_ID:当前文件中最大 segment id + 1,用于段分配时的 seg id 计数器FSP_FRAG_N_USED:FSP_FREE_FRAG 链表上已被使用的页数,用于快速计算该链表上可用空闲页数每一个 tablespace 都维护若干(双向)链表:FSP_SEG_INODES_FULL:已被完全用满的 INODE pageFSP_SEG_INODES_FREE:存在空闲 entry 的 INODE pageFSP_FREE:extent 中所有页都未被使用时,放到该链表上FSP_FREE_FRAG:extent 中部分页被使用(不属于任何 segment,被共享使用)FSP_FULL_FRAG:extent 中所有页都被使用(不属于任何 segment,被共享使用) XDES entry:每个 entry 用于描述一个 extent 的所有 数据页。每个 entry 包含 128 bit(XDES_BITMAP),2 bits 描述 extent 中的一个页:XDES entry 与 extent 一一对应。现在我们有了 tablespace 的 big picture。在 B-tree index 内部,中间节点和叶子节点分为两个 segment,即占用两个 INODE entry(Inode 5 和 Inode 6)。这两个 file segment 共用根节点作为 segment header page。保存着 segment header(图中 FSEG Header)。每个 INODE entry 记录着若干个 extent list(list 上的对象是 XDES entry)追踪着该 segment 的空间使用情况。 页结构Page Directory 中存放了记录的页相对位置,这些记录称为槽,一个槽中可能包含多个记录。File Trailer:检测页是否已经完整地写入磁盘。 
  • [整体安全] 【漏洞预警】Oracle MySQL Server输入验证错误漏洞(CVE-2022-21367)
    漏洞描述:甲骨文公司,全称甲骨文股份有限公司(甲骨文软件系统有限公司),是全球最大的企业级软件公司,总部位于美国加利福尼亚州的红木滩。1989年正式进入中国市场。2013年,甲骨文已超越 IBM ,成为继 Microsoft 后全球第二大软件公司。Oracle MySQL Server是美国甲骨文(Oracle)公司的一款关系型数据库。Oracle MySQL Server存在输入验证错误漏洞,攻击者可利用该漏洞未经授权更新、插入或删除对MySQL Server可访问数据的访问。漏洞危害:Oracle MySQL 的MySQL Server 产品中的漏洞。受影响的版本包括 5.7.36 及更早版本和 8.0.27 及更早版本。易于利用的漏洞允许高特权攻击者通过多种协议进行网络访问,从而破坏MySQL服务器。成功攻击此漏洞可导致未经授权的能力,导致MySQL服务器挂起或频繁可重复的崩溃,以及未经授权的更新,插入或删除对某些MySQL服务器可访问数据的访问。影响范围:Oracle MySQL Server <=5.7.36Oracle MySQL Server <=8.0.27漏洞等级:  中危修复方案:厂商已发布了漏洞修复程序,请及时关注更新:https://www.oracle.com/security-alerts/cpujan2022.html
总条数:1406 到第
上滑加载中