最近在作项目的时候,遇到了一些大数据量的操做,有大批量的CRUD的操做,一开始的实现的方案通过性能测试,发现性能并非很好,而后开始审查代码,对相关能够提高性能的操做进行了优化,这里分享给你们。sql
首先我这里不讲索引相关的内容以及数据库相应参数的优化,这里假设你对索引已经有了相关的了解了,我总结了下我此次的优化,主要两个原则:数据库
为模拟运行场景,我这里建了一个表,并往里面添加了300w条数据,表结构以下:网络
CREATE TABLE `tb_big_data` ( `id` int(11) NOT NULL AUTO_INCREMENT, `weixin_id` varchar(64) NOT NULL, `openid` varchar(64) NOT NULL, `status` int(3) NOT NULL, `gmt_create` datetime NOT NULL, `gmt_modified` datetime NOT NULL, PRIMARY KEY (`id`), KEY `weixin_id_gmt_create_openid` (`weixin_id`,`gmt_create`,`openid`) ) ENGINE=InnoDB AUTO_INCREMENT DEFAULT CHARSET=utf8
分页查询老生常谈,网上各类优化方法都不少,这里就不说起了,这里只是分享一个小技巧:性能
如何在使用最普通的limit的时候提升性能?测试
假设咱们如今有一条这样的SQL:大数据
SELECT * FROM `tb_big_data` where weixin_id ='gh_266a30a8a1f6' and gmt_create > '2017-10-10 00:00:00' order by id asc limit 800000, 100; 执行时间:100 rows in set (1.53 sec)
假如咱们如今不能进行其余优化,好比传入最小id,分表查询等策略,以及不进行SQL预热,怎么提升这条SQL的速度呢?
其实很简单咱们只须要一个in操做便可:优化
SELECT * FROM `tb_big_data` t1 where t1.id in ( SELECT tt.id FROM ( SELECT id FROM `tb_big_data` t2 where weixin_id = 'gh_266a30a8a1f6' and gmt_create > '2017-10-10 00:00:00' order by t2.id asc limit 800100, 100 ) as tt); 执行时间:100 rows in set (1.17 sec)
能够看出只需稍加修改,SQL的效率能够提升30%~40%,并且在单条数据记录越大的状况下效果越好,固然这不是最好的分页方法,这只是一个小技巧;3d
如今有一个需求咱们如今有一个用户的列表(用户的惟一标识为openid)而后咱们须要判断用户在当天是否有过相应的记录;code
这是问题其实很简单,咱们首先一想到的操做就是循环这个列表一个一个判断,很简单也很好实现,可是真正测试的时候发现性能却不好,尤为在数据量大的状况下,倍数级增加,这里有有网络数据传输消耗的时间和SQL自己的执行时间;索引
假设咱们如今执行一条如下的SQL:
SELECT * FROM `tb_big_data` WHERE weixin_id ='gh_266a30a8a1f6' and gmt_create > '2017-10-13 00:00:00' and openid='2n6bvynihm5bzgyx'; 执行时间:1 row in set (0.95 sec)
如今若是咱们执行100次,不敢想象会是什么状况,庆幸本身发现了这个问题,由于在数据量少的状况下,这个问题表现的并非那么严重,其实咱们稍加改变就能以另外一种高效的方式解决这个问题:
SELECT * FROM `tb_big_data` WHERE weixin_id ='gh_266a30a8a1f6' and gmt_create > '2017-10-13 00:00:00' and openid in ('2n6bvynihm5bzgyx','1stbvdnl63de2q37','3z8552gxzfi3wy27'...); 执行时间:100 row in set (1.05 sec)
发现了没有,仍是用in,并且执行时间几乎与单条查询的时间同样,可见只是单一这一部分处理就能够提高了很大的性能。
这个跟上一点有一个类似点,那就是减小SQL执行,上面只是查询而已,而当出现大批量的CUD的操做时,执行每条SQL,数据库都会进行事务处理,这将会消耗大量的时间,并且极端状况下会引发大批量SQL等待没法执行,致使业务出错,正是由于这些缘由,咱们在一些适当的状况下可使用批处理来解决这个问题。
批量插入比较简单,也比较经常使用,这里就给一下基本语法:
INSERT INTO table_name (field1,filed2,...) values (value11,value12,...),(value21,value22,...),...
我先举个简单的例子,咱们如今来根据一些条件来更新数据,具体SQL以下:
update `tb_big_data` set status = 2 WHERE weixin_id ='gh_266a30a8a1f6' and gmt_create > '2017-10-13 00:00:00' and openid = '2n6bvynihm5bzgyx'; Query OK, 1 row affected (2.28 sec) Rows matched: 1 Changed: 1 Warnings: 0
很惊讶,咱们只是更新了一条记录,并且更新条件上是有复合索引的,没想到速度还那么慢,能够想象若是咱们批量更新数据,那得耗时多少;
可是咱们看一下另外一条SQL:
update `tb_big_data` set status = 1 WHERE id = 900098; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0
上面的id值为以前条件筛选出来的记录的id,是否是很惊讶,怎么这条SQL执行的时间几乎不须要什么时间,因此咱们能够利用这个特色和批量查询简化批量更新,虽然这种方式不能让性能到最优,可是也能提高很大了,我进行了一个测试,根据相应条件批量更新100条数据:
方式 | 直接批量更新 | 先批量查主键再批量更新 |
---|---|---|
耗时 | 289.12s | 1.342s |
能够看出这种方式相对对于普通方式来讲,性能提高巨大,具体执行的时候咱们也能够将这些SQL放在一个事务提交,减小数据库事务次数,但只这是一种在代码层面上的优化;
另外咱们能够利用MySQL提供的特殊语法进行批量更新,具体语法为:
#语法 INSERT INTO table_name (id,field1,field2,...) VALUES (id1,value11,value12,...),(id1,value11,value12,...),... on duplicate key update field = VAULES(field); #使用例子 INSERT INTO `tb_big_data` (id,weixin_id,openid,gmt_create,status) values (1,'gh_266a30a8a1f6','w9q8fmodytjgppsr','2017-10-13 12:00:00',3),(2,'gh_266a30a8a1f6','bu1flmch4i8eegzf','2017-10-13 12:00:00',3) on duplicate key update status = VAULES(status);
通过测试这种方式在数据量小的状况下与上述方式效率差很少,可是随着数据量愈来愈大,性能也愈来愈好,缺点的话主要传输的数据量很大,不须要更新的字段也须要传输。
另外也不推荐大量数据的批量更新,一次不要超过1000条为好。
总的来讲,SQL优化是一门细心的学问,须要不断去尝试,测试,找到最优方式,另外还有一点就是要结合实际状况,综合考虑选择合适的方式。