前言:希望通过这篇文章,让MySQL5.7.18的用户知道分区表使用中的陷阱,避免继续踩下去在这个版本上。同时,通过源码的分享,找到升级MySQL5.0时分区表性能下降的根本原因。问题描述在MySQL5.7中,有许多与性能相关的改进。包括临时表相关的性能提升、连接建立速度的优化以及复制分布相关的性能提升等,基本上不需要做配置改动,只需要升级到5.7版本就可以带来很多性能提升。在测试环境中,我们将数据库升级到5.7.18版本,验证MySQL5.7.18版本是否符合我们的预期。观察运行一段时间后,有开发反馈数据库性能较之前的5.6.21版本有所下降。主要特点是遇到锁超时比较多。开发的另一个反馈是,性能下降相关的表都是分区表。更新都是基于主键的。这一反馈引起了我们的注意。我们做了如下尝试:数据库版本为5.7.18,分区表保留,性能会下降。数据库版本为5.7.18,表调整为非分区表,性能正常。将数据库版本回滚到5.6.21版本,保留分区表,性能正常通过以上测试。我们粗略判断,这次性能下降与MySQL5.7版本升级有关。问题复现测试环境的数据库表结构比较庞大,调用关系也比较复杂。为了进一步分析和定位问题,我们拆开来搭建一个简单的复现流程如下//创建测试分区表t2:CREATETABLE`t2`(`id`INT(11)NOTNULL,`dt`DATETIMENOTNULL,`data`VARCHAR(10)DEFAULTNULL,PRIMARYKEY(`id`,`dt`),KEY`idx_dt`(`dt`))ENGINE=INNODBDEFAULTCHARSET=latin1/*!50100PARTITIONBYRANGE(to_days(dt))(PARTITIONp20170218VALUESLESSTHAN(736744)ENGINE=InnoDB,PARTITIONp20170219VALUESLESSTHAN(736745)ENGINE=InnoDB,PARTITIONpMaxVALUESLESSTHANMAXVALUEESENGINE=InnoDB)*///插入测试数据INSERTINTOt2(),'1');插入t2值(2,现在(),'2');插入t2值(3,现在(),'3');//SESSION1对id=1的记录做了一次更新操作,事务还没有提交。开始;更新t2SETDATA='12'WHEREid=1;//SESSION2更新id=2的记录。BEGIN;UPDATEt2SETDATA='21'WHEREid=2;在SESSION2中,我们发现这个更新操作一直在等待。ID是主键。按理说主键id=1记录的更新不会影响主键id=2记录的更新。查询information_schema下的innodb_locks表。该表用于记录InnoDB事务尝试申请但未获取的锁,以及阻塞其他事务的事务所拥有的锁。有两条记录:此时观察innodb_locks表,事务id=40021锁定了第三页的第二行,导致事务id=40022无法进行。如果我们将数据库回滚到5.6.21版本,则无法重现上述场景。根据innodb_locks表提供的信息进一步分析,我们知道问题是InnoDB锁定了不合适的行。表是一个内存存储引擎。我们在内存存储引擎的插件界面打断点,得到如下堆栈信息。确保是红框中的部分,将锁信息写入到innodb_locks表中。并且在函数fill_innodb_locks_from_cache中确认,写入每一行的数据是从下面代码中的Cache对象中获取的。我们知道事务锁信息保存在Cache中,那么我们还需要进一步了解Cache中的数据是如何添加的。通过搜索缓存对象在innodb代码中出现的位置找到函数add_lock_to_cache。在此函数中设置断点调试后,发现其内容与innodb_locks表中填写的数据一致。确定这个函数使用的锁对象,就是我们要找的锁对象。检查在哪里使用了lock_t类型。经过筛选调试,发现在函数RecLock::lock_add中,将生成的行锁添加到锁所在的事务链表中。RecLock::lock_add函数可以推断出行锁产生的原因。因此,通过在该函数上设置断点,查看函数栈,在如下栈中定位到红框内的函数:跟踪Partition_helper::handle_ordered_index_scan的如下代码。根据这段代码分析,m_part_spec.end_part决定了指定最大行数锁定,这就是产生行锁异常的原因。最终问题归结为生成m_part_spec.end_part的原因。通过检查end_part在什么地方使用,变量在使用前的初始设置值最终定位到get_partition_set函数中。从代码中可以看出,对于单条记录的每次更新操作,索引扫描加锁时,分区表个数相同的行数被加锁。这是根本原因。验证结论根据前面的分析,单条记录的每次更新操作都会锁定分区表数量相同的行数。我们试图验证我们的发现。添加以下两条记录:INSERTINTOt2VALUES(4,NOW(),'4');插入t2值(5,现在(),'5');//SESSION1对id=1的记录做了一次更新操作,事务还没有提交。开始;更新t2SETDATA='12'WHEREid=1;//SESSION2现在对id=4的记录进行更新。BEGIN;UPDATEt2SETDATA='44'WHEREid=4;我们发现更新到id=4可以正常工作。id=1的updates不受影响。这是因为id=4的记录超过了测试用例的分区数,不会被锁住。在实际应用中,分区表定义的分区数量不会像测试用例那样只有3个,而是几十个甚至上百个。这样加锁的结果会加剧更新时的锁冲突,导致事务处于锁等待状态。如下图所示,每个事务都有N个行锁,所以这些加锁的记录相互覆盖的可能性大大增加,导致并发和效率下降。结论通过以上分析,我们非常确定这应该是MySQL5.7版本的回归。我们向开源社区提交了一个错误。Oracle确认这是一个问题,需要进一步分析和调查此错误。大家注意点,别迷路了,以上就是本文的全部内容,能看到这里的都是人才。前面说了PHP的技术点很多,也是因为太多了,写的太多了,写完了也不会看太多,所以我这里整理成了PDF和文档,有需要的可以点击进入秘籍:PHP+「平台」更多学习内容可以访问【比大厂】优质PHP架构师教程目录,只要会看,薪资高会更上一层楼(持续更新)。以上内容希望对大家有所帮助,很多PHPer在进阶的时候总会遇到一些问题和瓶颈。业务代码写多了就没有方向感。架构、高扩展、高性能、高并发、服务器性能调优、TP6、laravel、YII2、Redis、Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等进阶知识点级别的干货,可以免费分享给大家,需要的话可以加入我的PHP技术交流群
