MySQL学习记录之KEY分区引发的血案

需求背景

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

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

  • 根据image_no查询和根据id查询;
  • 存量数据2kw;
  • 日增长4w左右;
  • 日查询量20w左右;
  • 非ToC系统,所以并发的天花板可见;

方案选择

根据上面对业务的分析,分库分表完全没有必要。单库分表的话,由于要根据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,截取原文如下:

Partitioning by key is similar to partitioning by hash, except that where hash partitioning employs a user-defined expression, the hashing function for key partitioning is supplied by the MySQL server. NDB Cluster uses MD5() for this purpose; for tables using other storage engines, the server employs its own internal hashing function which is based on the same algorithm as PASSWORD().

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

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

/**
 Calculate part_id for (SUB)PARTITION BY KEY
 @param file        Handler to storage engine
 @param field_array     Array of fields for PARTTION KEY
 @param num_parts      Number of KEY partitions
 @param func_value[out]   Returns calculated hash value
 @return Calculated partition id
*/
inline
static uint32 get_part_id_key(handler *file,
               Field **field_array,
               uint num_parts,
               longlong *func_value)
{
 DBUG_ENTER("get_part_id_key");
 // 计算分区字段的hash值
 *func_value= file->calculate_key_hash_value(field_array);
 // 对分区数取模
 DBUG_RETURN((uint32) (*func_value % num_parts));
}

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

一个同事根据password函数,分析并测出,key分区,只能指定分区数目为质数,才能保证每个分区都有数据。我测了下,从11个分区,到17个分区。 只有11,13,17 ,这3个分区的数据是基本平均分布的。

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

  1. 如果设置40,64,128等偶数个分区数(PARTITIONS 64),会导致编号为奇数的分区(p1, p3, p5, p7, … p2n-1)完全插不进数据;
  2. 如果设置63,121(PARTITIONS 63)这种奇数但非质数个分区数,所有分区都会有数据,但是不均匀;
  3. 如果设置137,31这种质数个分区数(PARTITIONS 137),所有分区都会有数据,并且非常均匀;

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

总结回顾

MySQL的KEY分区这么大的使用陷阱,居然在官方上没有任何说明,这让笔者感到非常震惊。此外还有MySQL bug:Bug #72428 Partition by KEY() results in uneven data distribution

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

到此这篇关于MySQL学习记录之KEY分区引发的血案的文章就介绍到这了,更多相关MySQL KEY分区血案内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • MySQL学习记录之KEY分区引发的血案

    需求背景 业务表tb_image部分数据如下所示,其中id唯一,image_no不唯一.image_no表示每个文件的编号,每个文件在业务系统中会生成若干个文件,每个文件的唯一ID就是字段id: 业务表tb_image的一些情况如下: 根据image_no查询和根据id查询: 存量数据2kw: 日增长4w左右: 日查询量20w左右: 非ToC系统,所以并发的天花板可见: 方案选择 根据上面对业务的分析,分库分表完全没有必要.单库分表的话,由于要根据image_no和id查询,所以,一种方案是冗余

  • MySQL分区之KEY分区详解

    目录 介绍 一.常规KEY 二.LINEAR KEY 三.分区管理 四.移除表的分区 总结 介绍 KEY分区和HASH分区相似,但是KEY分区支持除text和BLOB之外的所有数据类型的分区,而HASH分区只支持数字分区,KEY分区不允许使用用户自定义的表达式进行分区,KEY分区使用系统提供的HASH函数进行分区.当表中存在主键或者唯一键时,如果创建key分区时没有指定字段系统默认会首选主键列作为分区字列,如果不存在主键列会选择非空唯一键列作为分区列,注意唯一列作为分区列唯一列不能为null.

  • Mysql学习心得之插入、更新、删除记录

    下面通过图文并茂的方式给大家详解Mysql学习心得之插入.更新.删除记录. 插入记录 复制代码 代码如下: Insert[]into] tb1_name[(col_name,-..)] 自动编号的字段,可以用values default Default 可以赋予默认值 复制代码 代码如下: INSERT USERS VALUES(NULL,'TOM','123',25,1); 可以同时插入多条记录 复制代码 代码如下: INSERT users VALUES(DEFAULT,'TOM','123

  • 一千行的MySQL学习笔记汇总

    本文详细汇总了MySQL学习中的各类技巧,分享给大家供大家参考. 具体如下: /* 启动MySQL */ net start mysql /* 连接与断开服务器 */ mysql -h 地址 -P 端口 -u 用户名 -p 密码 /* 跳过权限验证登录MySQL */ mysqld --skip-grant-tables -- 修改root密码 密码加密函数password() update mysql.user set password=password('root'); SHOW PROCE

  • MySQL学习笔记之数据定义表约束,分页方法总结

    本文实例讲述了MySQL学习笔记之数据定义表约束,分页方法.分享给大家供大家参考,具体如下: 1. primary key 主键 特点:主键是用于唯一标识一条记录的约束,一张表最多只能有一个主键,不能为空也不能重复 create table user1(id int primary key,name varchar(32)); mysql> insert into user1 values(1,'hb'); Query OK, 1 row affected (0.10 sec) mysql>

  • MySQL 外键(FOREIGN KEY)用法案例详解

    引子:把所有数据都存放于一张表的弊端 表的组织结构复杂不清晰 浪费空间 扩展性极差 为了解决上述的问题,就需要用多张表来存放数据. 表与表的记录之间存在着三种关系:一对多.多对多.一对一的关系. 处理表之间关系问题就会利用到FOREIGN KEY 多对一关系: 寻找表与表之间的关系的套路 举例:雇员表:emp表   部门:dep表 part1: 先站在表emp的角度 去找表emp的多条记录能否对应表dep的一条记录. 翻译2的意义: 左表emp的多条记录==>多个员工 右表dep的一条记录==>

  • mysql表的四种分区方式总结

    目录 1.什么是表分区? 2.表分区与分表的区别 3.表分区有什么好处? 4.分区表的限制因素 5.如何判断当前MySQL是否支持分区? 6.MySQL支持的分区类型有哪些? 6.1.RANGE分区 6.1.1.根据数值范围 6.1.2.根据TIMESTAMP范围 6.1.3.根据DATE.DATETIME范围 6.1.4.根据多列范围 6.1.5.RANGE分区在如下场合特别有用 6.2.LIST分区 6.3.HASH分区 6.4.KEY分区 总结 1.什么是表分区? mysql数据库中的数据

  • MySQL学习之完整性约束详解

    数据完整性指的是数据的一致性和正确性.完整性约束是指数据库的内容必须随时遵守的规则.若定义了数据完整性约束,MySQL会负责数据的完整性,每次更新数据时,MySQL都会测试新的数据内容是否符合相关的完整性约束条件,只有符合完整性的约束条件的更新才被接受. 1.主键约束 主键就是表中的一列或多个列的组合,其值能唯一地标识表中的每一行.MySQL为主键列创建唯一性索引,实现数据的唯一性.在查询中使用主键时,该索引可用来对数据进行快速访问.通过定义PRIMARY KEY约束来创建主键,而且PRIMAR

  • MySQL学习第六天 学习MySQL基本数据类型

    还记得上一篇学习的内容吗?不记得再看一看MySQL学习第五天 MySQL数据库基本操作,温故可以知新! 数据类型是指列.存储过程参数.表达式和局部变量的数据特征,它决定了数据的存储方式,代表了不同的信息类型.MySQL中常用的的数据类型包括:数值类型.日期和时间类型和字符串类型等.  一.数值类型 MySQL支持所有标准SQL中的数值类型,其中包括严格数据类型(INTEGER.SMALLINT.DECIMAL.NUMBERIC),以及近似数值数据类型(FLOAT.REAL.DOUBLE.PRES

  • MySQL学习笔记之数据的增、删、改实现方法

    本文实例讲述了MySQL学习笔记之数据的增.删.改实现方法.分享给大家供大家参考,具体如下: 一.增加数据 插入代码格式: insert into 表明 [列名-] values (值-) create table test21(name varchar(32)); insert into test21 (name) values ('huangbiao'); 插入原则: 1.插入的数据应与字段的数据类型相同 2.数据的大小应该在列的规定范围内 3.在values中列出的数据位置必须与被加入的列

随机推荐