Mysql 自定义HASH索引带来的巨大性能提升----[挖坑篇]
有这样一个业务场景,需要在2个表里比较存在于A表,不存在于B表的数据。表结构如下:
T_SETTINGS_BACKUP | CREATE TABLE `T_SETTINGS_BACKUP` (
`FID` bigint(20) NOT NULL AUTO_INCREMENT,
`FUSERID` bigint(20) NOT NULL COMMENT '用户ID',
`FDEVICE` varchar(64) NOT NULL DEFAULT '' COMMENT '用户设备号(SN)',
`FAPPID` varchar(64) NOT NULL DEFAULT '' COMMENT '应用ID',
`FKEYID` varchar(32) NOT NULL DEFAULT '' COMMENT '设置项ID',
`FCONTENT` varchar(2000) NOT NULL DEFAULT '' COMMENT '设置项内容',
`FUPDATETIME` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '修改时间',
`FCREATETIME` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '创建时间',
PRIMARY KEY (`FID`),
UNIQUE KEY `UDX_USERID_DEVICE_APPID_KEYID` (`FUSERID`,`FDEVICE`,`FAPPID`,`FKEYID`)
) ENGINE=InnoDB AUTO_INCREMENT=21934 DEFAULT CHARSET=utf8mb4
暂定义上表为A表,记录数:21933
B表表结构如下,记录数:4794959
CREATE TABLE `meizu_device_tmp_1` (
`id` int(11) unsigned NOT NULL DEFAULT '',
`imei` bigint(20) NOT NULL DEFAULT '' COMMENT 'imei',
`sn` varchar(20) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT 'sn',
UNIQUE KEY `imei` (`imei`),
UNIQUE KEY `sn` (`sn`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
A的FDEVICE和B的SN是关联字段,现在要求出FDEVICE在A不在B的记录数。自然想到下面的LEFT JOIN
mysql> explain select A.fdevice FROM T_SETTINGS_BACKUP A left JOIN meizu_device_tmp_1 B ON A.FDEVICE=B.sn where B.sn is null;
+----+-------------+-------+-------+---------------+-------------------------------+---------+------+---------+--------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+-------------------------------+---------+------+---------+--------------------------------------+
| 1 | SIMPLE | A | index | NULL | UDX_USERID_DEVICE_APPID_KEYID | 654 | NULL | 22232 | Using index |
| 1 | SIMPLE | B | index | NULL | sn | 62 | NULL | 4772238 | Using where; Using index; Not exists |
+----+-------------+-------+-------+---------------+-------------------------------+---------+------+---------+--------------------------------------+
2 rows in set (0.00 sec)
执行时间1小时以上,等不出结果直接KILL掉了。
分析上面的执行计划,两个表都用到了覆盖索引,每个表都没有过滤条件,所以需要扫描全部行,2W乘以470W是个巨大的数字,执行器在不停的做内循环的判断,直到完成22232*4772238次。除了这个循环次数巨大外,这个执行计划还有2个需要考量的地方
1)type=index 2)key_len
type=index 执行效率仅高于全表扫描,在某些情况下比全部扫描更差。key_len比较大,说明索引太长。A表的索引是个4字段的组合索引,有用的比较字段只有FDEVICE,为了覆盖索引优化器把全部字段都加入判断了。
对于key_len 有两个疑问 1)为什么A表的key_len=654? 2)为什么B表的ken_len=62?
优化key_len, 考虑到业务特性,FUSERID肯定大于0,把SQL改一下,执行计划看起来好一点了,type=range,key_len=8,实际上对A表只用到了FUSERID字段索引,最左前缀,FDEVICE通过WHERE判断。
mysql> desc select A.fdevice FROM T_SETTINGS_BACKUP A left JOIN meizu_device_tmp_1 B ON A.FDEVICE=B.sn where A.fuserid>0 and B.sn is null;
+----+-------------+-------+-------+-------------------------------+-------------------------------+---------+------+---------+--------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+-------------------------------+-------------------------------+---------+------+---------+--------------------------------------+
| 1 | SIMPLE | A | range | UDX_USERID_DEVICE_APPID_KEYID | UDX_USERID_DEVICE_APPID_KEYID | 8 | NULL | 11116 | Using where; Using index |
| 1 | SIMPLE | B | index | NULL | sn | 62 | NULL | 4911049 | Using where; Using index; Not exists |
+----+-------------+-------+-------+-------------------------------+-------------------------------+---------+------+---------+--------------------------------------+
2 rows in set (0.01 sec)
执行时间依旧很长,等不了直接KILL了。
优化到这一步,还有什么别的招数,可以提高执行性能的?似乎已经到了尽头。
回顾下两表的关联字段,A.FDEVICE=B.sn,两个字段都是字符串,在数据类型上考虑,自然想到,是不是可以把记录比较字段从字符串的比较,改成数字的比较?这是个优化的方向。在计算机里底层数据都是01010这样,只需要把数字换算成0101就可以做等值比较了,但是变成字符,需要先去字符编码表找到字符对应的数字,在把数字换算成0101,这里多出一步查找操作。另一方面字符占用的空间比数字要大很多,一个页内能存下的item条目比数字的要少,这会导致更多的数据页读取。
根据这个方向,尝试使用自定义HASH索引,常见的HASH函数有MD5,password,crc32,sha1等,只有crc32哈希之后的值的数字型的。
mysql> select md5('sdsafa'),password('sdsafa'),crc32('sdsafa'),SHA1('sdsafa');
+----------------------------------+-------------------------------------------+-----------------+------------------------------------------+
| md5('sdsafa') | password('sdsafa') | crc32('sdsafa') | SHA1('sdsafa') |
+----------------------------------+-------------------------------------------+-----------------+------------------------------------------+
| c5067032ca64a35620fc5c75aa42265c | *45ABB21DBD1E6A5659E05F1EBAF589A3B39EB835 | 1766538443 | b9349f6a0b8138e3e6461745fd257678eefeb9a2 |
+----------------------------------+-------------------------------------------+-----------------+------------------------------------------+
1 row in set (0.00 sec)
在表里加个字段记录hash之后的值,并对这个字段加上索引。
mysql> CREATE TABLE `meizu_device_tmp_3` (
-> `id` int(11) unsigned NOT NULL DEFAULT '',
-> `imei` bigint(20) NOT NULL DEFAULT '' COMMENT 'imei',
-> `sn` varchar(20) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT 'sn',
-> `hash_sn` bigint(20) DEFAULT NULL,
-> UNIQUE KEY `imei` (`imei`),
-> KEY `hash_sn` (`hash_sn`)
-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ;
Query OK, 0 rows affected (0.20 sec) mysql> insert into meizu_device_tmp_3 select id,imei,sn,crc32(sn) from meizu_device_tmp_1;
Query OK, 4794959 rows affected (1 min 50.31 sec)
Records: 4794959 Duplicates: 0 Warnings: 0 mysql> select * from meizu_device_tmp_3 limit 1;
+----------+---------+--------------------+------------+
| id | imei | sn | hash_sn |
+----------+---------+--------------------+------------+
| 23930528 | 1311265 | MX21CA2ALHR2460302 | 2330453935 |
+----------+---------+--------------------+------------+
1 row in set (0.00 sec)
查询时,关联字段先crc32计算后,再比较,这样就变成了数字和数字的比较了,被驱动表比较字段也有索引。
但是crc32算法可能存在hash碰撞,也就是不同的值hash出来的结果是一样的,这就“撞”上了。为了避免碰撞导致的比较结果不准确,在hash比较之后,再做一次原值的比较。
优化之后的查询语句是这样的
mysql> desc select A.fdevice FROM T_SETTINGS_BACKUP A left JOIN meizu_device_tmp_3 B ON crc32(A.FDEVICE)=B.hash_sn and A.fdevice=B.sn where B.sn is null;
+----+-------------+-------+-------+---------------+-------------------------------+---------+------+-------+-------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+-------------------------------+---------+------+-------+-------------------------+
| 1 | SIMPLE | A | index | NULL | UDX_USERID_DEVICE_APPID_KEYID | 654 | NULL | 22232 | Using index |
| 1 | SIMPLE | B | ref | hash_sn | hash_sn | 9 | func | 1 | Using where; Not exists |
+----+-------------+-------+-------+---------------+-------------------------------+---------+------+-------+-------------------------+
2 rows in set (0.00 sec)
巨大的改变,被驱动表的rows=1. SQL执行时间0.38秒。
hash索引有这么大的好处,但是也存在不少缺点
1)hash不能处理范围比较,只能处理等值比较。
2)hash不能做排序,hash出来的结果是随机分布的。
3)hash不支持部分索引,如index a(10)就不支持。
4)hash无法覆盖索引
5)hash有碰撞,碰撞得比较厉害时,处理碰撞的代价就比较高。
CRC32算法:http://wenku.baidu.com/view/465dca06e87101f69e31951f.html
自定义hash索引:http://www.lizhonghaosc.cn/mysql-chuang-jian-zi-ding-yi-ha-xi-suo-yin/
Mysql 自定义HASH索引带来的巨大性能提升----[挖坑篇]的更多相关文章
- Mysql 自定义HASH索引带来的巨大性能提升----[真相篇]
推倒重来 俗话说no zuo no die why you try,这时候我又忍不住zuo了,吭哧吭哧的把解决过程发上博客,向全世界宣布,哥又搞定个难题. 剧情的发展往往是看起来主角完全掌握了局势的情 ...
- Mysql 自定义HASH索引带来的巨大性能提升
有这样一个业务场景,需要在2个表里比较存在于A表,不存在于B表的数据.表结构如下: T_SETTINGS_BACKUP | CREATE TABLE `T_SETTINGS_BACKUP` ( `FI ...
- MySQL 8.0 —— CATS事务调度算法的性能提升
原文地址:https://mysqlserverteam.com/contention-aware-transaction-scheduling-arriving-in-innodb-to-boost ...
- SQL优化系列(三)- 用最少的索引获得最大的性能提升
从全局出发优化索引 对于高负载的数据库,如何创建最少的索引,让数据库的整体性能提高呢?例如,对于100 条SQL语句,如何创建最佳的5条索引? SQL自动优化工具SQL Tuning Expert P ...
- MySQL系列(六)--索引优化
在进行数据库查询的时候,索引是非常重要的,当然前提是达到一定的数据量.索引就像字典一样,通过偏旁部首来快速定位,而不是一页页 的慢慢找. 索引依赖存储引擎层实现,所以支持的索引类型和存储引擎相关,同一 ...
- InnoDB关键特性之自适应hash索引
一.索引的资源消耗分析 1.索引三大特点 1.小:只在一个到多个列建立索引 2.有序:可以快速定位终点 3.有棵树:可以定位起点,树高一般小于等于3 2.索引的资源消耗点 1.树的高度,顺序访问索引的 ...
- 数据库索引------B-Tree 索引和 Hash 索引的对比
对于 B-tree 和 hash 数据结构的理解能够有助于预测不同存储引擎下使用不同索引的查询性能的差异,尤其是那些允许你选择 B-tree 或者 hash 索引的内存存储引擎. B-Tree 索引的 ...
- B-Tree 索引和 Hash 索引的对照
对于 B-tree 和 hash 数据结构的理解可以有助于预測不同存储引擎下使用不同索引的查询性能的差异.尤其是那些同意你选择 B-tree 或者 hash 索引的内存存储引擎. B-Tree 索引的 ...
- Innodb关键特性之自适用Hash索引
一.索引的资源消耗分析 1.索引三大特点 1.小:只在一个到多个列建立索引 2.有序:可以快速定位终点 3.有棵树:可以定位起点,树高一般小于等于3 2.索引的资源消耗点 1.树的高度,顺序访问索引的 ...
随机推荐
- 1.jquery的变量赋予方式
1.首先通过将变量赋予 $_ = window.$ _jQuery = window.JQuery 2.将函数赋给两个全局变量 window.jQuery = window.$ = jQuery 3. ...
- 图的深度优先和广度优先遍历(图以邻接表表示,由C++面向对象实现)
学习了图的深度优先和广度优先遍历,发现不管是教材还是网上,大都为C语言函数式实现,为了加深理解,我以C++面向对象的方式把图的深度优先和广度优先遍历重写了一遍. 废话不多说,直接上代码: #inclu ...
- 坑爹的属性,android:descendantFocusability用法简析
开发中很常见的一个问题,项目中的listview不仅仅是简单的文字,常常需要自己定义listview,自己的Adapter去继承 BaseAdapter,在adapter中按照需求进行编写,问题就出现 ...
- CSS选择器和jQuery选择器的区别与联系之一
到底什么是选择器?我们通过常接触的CSS选择器和jQuery选择器理解一下,我们知道CSS是用于分离网页的结构和表现的,也就是说对于一个网页,HTML定义网页的结构,CSS描述网页的样子,一个很经典的 ...
- 我的qq邮箱的GPG公钥
呵呵,赶一波潮流,我的邮箱 175420840@qq.com 的GPG公钥如下,也可以在这里直接下载.具体可参见阮一峰的<GPG入门教程>. -----BEGIN PGP PUBLIC K ...
- android baseActivity
package newdemo.jeno.designdemo.activitynew; import android.os.Bundle;import android.support.annotat ...
- 黑马程序员——OC语言Foundation框架 (2) NSArray NSSet NSDictionary\NSMutableDictionary
Java培训.Android培训.iOS培训..Net培训.期待与您交流! (以下内容是对黑马苹果入学视频的个人知识点总结) (一)NSArray 1>NSArray :不可变数组 ①创建方法 ...
- android贪吃蛇(超级简陋版)
public class body { public int ax;//代表X周变量 public int ay;//代表Y轴变量 public int getAx() { return ax; } ...
- [深入Python]Python的私有变量
Python没有真正的私有变量.内部实现上,是将私有变量进程了转化,规则是:_<类名><私有变量> 下面的小技巧可以获取私有变量: class Test(object): de ...
- MVC的过滤器
过滤器分类: Action过滤器 View结果渲染过滤器 全局错误异常过滤器 身份验证过滤器 1.Action过滤器:在Action执行之前和执行之后分别干一些事 接口:(IA ...