MySQL之KEY分区引发的血案

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL之KEY分区引发的血案相关的知识,希望对你有一定的参考价值。

参考技术A

业务表tb_image部分数据如下所示,其中id唯一,image_no不唯一。image_no表示每个文件的编号,每个文件在业务系统中会生成若干个文件,每个文件的唯一ID就是字段id:

业务表tb_image的一些情况如下:

根据上面对业务的分析,分库分表完全没有必要。单库分表的话,由于要根据image_no和id查询,所以,一种方案是冗余分表(即一份数据以image_no为分片键保存,另一份数据以id为分片键保存);另一种方案是只以image_no为分片键,而基于id的查询需求,业务层进行结果归并或者引入第三方中间件。

考虑到单库分表比较复杂,所以决定使用分区特性,而且容量评估分区表方案128个分区(每个分区数据量kw级别)完全能保证业务至少稳定运行15年(图中橙色部分是比较贴合自身业务实际增长情况):

另外,由于RANGE, LIST, HASH分区都不支持VARCHAR列,所以决定采用KEY分区,官方介绍它的原理是以mysql内置hash算法然后对分区数取模。

选定分片键为image_no,并且决定分区数为128后,就要灌入数据进行可行性和性能测试了。分区数选择128的原因是:11亿/1kw=110≈128,另外程序员情节,喜欢用2的N次方,你懂的。然而, 这个分区数128就是一切噩梦的开始

我尝试先插入10w数据到128个分区中,插入后,让我惊讶的现象出现了: 所有奇数编号分区(p1, p3, p5, ... , p2n-1)中居然没有一条数据 ,同时,任何一个偶数编号分区却有很多的数据,而且还不是很均匀。如下图所示:

说明 :奇数编号分区的ibd文件大小都是112k,这是创建分区表时初始化大小,实际并没有任何数据。我们可以通过SQL: select partition_name, partition_expression, table_rows from information_schema.partitions where table_schema = schema() and table_name=\'image_subpart\'; 验证,其部分结果如下图所示:

难道10w条数据还不够说明问题?平均下来每个分区可是有近800条数据!好吧,来点猛的:我再插入990w条数据,总计1kw数据。结果还是一样,奇数编号分区没有数据,偶数编号都有分区。

我们再来回想一下KEY分区的原理: 通过MySQL内置hash算法对分片键计算hash值后再对分区数取模 。这个原理也可以从MySQL官网找到,请戳链接: 22.2.5 KEY Partitioning: https://dev.mysql.com/doc/refman/5.7/en/partitioning-key.html ,截取原文如下:

这个世界上不会有这么渣渣的hash算法吧? 随便写个什么算法也不至于这么不均匀吧?这时候我怀疑是否有一些什么配置引起的。但是 show variables 中并没有任何与partition相关的变量。

这个时候,一万匹马奔腾而过。会不会是文档和源码不同步导致的?好吧,看MySQL的源码,毕竟, 源码才是最接近真相的地方 。KEY分区相关源码在文件 sql_partition.cc 中,笔者截取部分关键源码,如下所示,初略观察,并没有什么不妥,先计算分区字段的hash值然后对分区数取模:

怀着绝望的心情,请出搜索引擎搜索:"KEY分区数据不均匀",搜索结果中的CSDN论坛( https://bbs.csdn.net/topics/390857704 )里有个民间高手 华夏小卒 回答如下:

这个时候,又是一万匹马奔腾而过。不过F**K的同时,心里也是有点小激动,因为可能找到解决办法了(虽然还不知道MySQL内置hash算法为毛会这样),最后笔者再次对KEY分区测试并总结如下:

如下图所示,是笔者把分区数调整为127并插入100w数据后的情况,通过SQL证明每个分区的数据量几乎一样:

MySQL的KEY分区这么大的使用陷阱,居然在官方上没有任何说明,这让笔者感到非常震惊。笔者还尝试Google搜索 mysql partition key uneven ,也有很多结果,例如 stackoverflow:https://stackoverflow.com/questions/38454354/mysql-uneven-distribution-of-data-into-partitions-when-using-key-partitioning ,此外还有MySQL bug: Bug #72428 Partition by KEY() results in uneven data distribution

正在看此文并有很强烈兴趣的同学,可以尝试更深入这个问题。笔者接下来也会找个时间,根据MySQL源码深入挖掘其hash算法的实现为什么对分区数如此敏感。

delete from t引发的血案

1、环境描述
一主两备,读写分离,主库一张大表,数据量3千万,执行delete from t;

2、备库状态
延迟几个小时,写入数据无法同步到备库,数据不一致,最终影响业务。
mysql([email protected]:(none))>show slave statusG;
mysql([email protected]:(none))>show slave statusG;
1. row
Slave_IO_State: Waiting for master to send event
Master_Host: 172.17.230.52
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000002
Read_Master_Log_Pos: 194
Relay_Log_File: relay-bin.000005
Relay_Log_Pos: 414
Relay_Master_Log_File: mysql-bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 404
Relay_Log_Space: 168492815
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 24049
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 1002
Master_UUID: 3f69546d-c6f3-11e8-97a0-00163e0cb7f6
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Reading event from the relay log
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set: 3f69546d-c6f3-11e8-97a0-00163e0cb7f6:2-9
Executed_Gtid_Set: 3f69546d-c6f3-11e8-97a0-00163e0cb7f6:1,
f571e3f1-c6f1-11e8-9a2d-00163e0efca3:1-31
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.00 sec)

ERROR:
No query specified

#top --一颗CPU跑死了,汗~~
Tasks: 91 total, 1 running, 90 sleeping, 0 stopped, 0 zombie
%Cpu0 : 0.0 us, 0.0 sy, 0.0 ni, 99.7 id, 0.3 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 : 0.0 us, 0.0 sy, 0.0 ni, 99.7 id, 0.3 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8010424 total, 4899140 free, 1894900 used, 1216384 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 5861632 avail Mem

3、处理方法
临时关闭读写分离,恢复业务,主库压力增加,暂时可支撑。

4、注意
类似这样的问题,完全可以避免,可以truncate table 。或者临时设置 set session binlog_format=‘statement‘; 再执行。或者结合业务创建分区表。总之,这样的问题不是数据库的问题,是使用者没有用好而已。

以上是关于MySQL之KEY分区引发的血案的主要内容,如果未能解决你的问题,请参考以下文章

mysql 分区PARTITIONS之基本使用

MySQL还能这样玩---第二篇之不为人知的分区

mysql进行分区之后所占的空间是否会变更大?

MySQL KEY分区

mysql key分区,分区数制定

MySql的时区(serverTimezone)引发的血案