MySQL的id关联和索引使用的实际优化案例
昨晚收到客服MM电话,一用户反馈数据库响应非常慢,手机收到load异常报警,登上主机后发现大量sql执行非常慢,有的执行时间超过了10s SELECT * FROM `sitevipdb`.`game_shares_buy_list` WHERE price>='2.00′ ORDER BY tran_id DESC LIMIT 10; 表结构为: CREATE TABLE `game_shares_buy_list` ( `tran_id` int(10) unsigned NOT NULL AUTO_INCREMENT,`………..' PRIMARY KEY (`tran_id`),KEY `ind_username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=3144200 DEFAULT CHARSET=utf8; 执行计划: root@127.0.0.1 : sitevipdb 09:10:22> explain SELECT * FROM `sitevipdb`.`game_shares_buy_list` WHERE price>='2.00′ ORDER BY tran_id DESC LIMIT 10; +―-+――――-+―――――――-+――-+―――――+―――+―――+――+――+――――-+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +―-+――――-+―――――――-+――-+―――――+―――+―――+――+――+――――-+ | 1 | SIMPLE | game_shares_buy_list | index | NULL | PRIMARY | 4 | NULL | 10 | Using where | +―-+――――-+―――――――-+――-+―――――+―――+―――+――+――+――――-+ 1 row in set (0.00 sec) 分析该sql的执行计划,由于tran_id是表的主键,所以查询根据主键降序顺序扫描,这样就可以不用排序, root@127.0.0.1 : sitevipdb 09:17:23> select price,count(*) as cnt from `game_shares_buy_list` group by price order by cnt desc limit 10; +――-+――-+ | price | cnt | +――-+――-+ | 1.75 | 39101 | | 1.68 | 38477 | | 1.71 | 34869 | | 1.66 | 34849 | | 1.72 | 34718 | | 1.70 | 33996 | | 1.76 | 32527 | | 1.69 | 27189 | | 1.61 | 25694 | | 1.25 | 25450 | 可以看到表中有大量的记录不是2.00的,所以这个时候不能在根据主键顺序扫描,在过滤记录; root@127.0.0.1 : sitevipdb 09:09:01> select count(*) from `game_shares_buy_list` where price>'2′; +―――-+ | count(*) | +―――-+ | 4087 | +―――-+ root@127.0.0.1 : sitevipdb 09:17:31> select count(*) from `game_shares_buy_list` ; +―――-+ | count(*) | +―――-+ | 1572100 | 从上面price的数据分布可以看出,price的分布相对还是比较集中的,如果在price建立索引,mysql也有可能认为由于需要回表的记录过多, root@127.0.0.1 : sitevipdb 09:24:53> alter table game_shares_buy_list add index ind_game_shares_buy_list_price(price); Query OK,0 rows affected (5.79 sec) 可以看到优化器虽然注意到了我们新加的索引,但是最终还是选择了primary来扫描; root@127.0.0.1 : sitevipdb 09:35:38> SELECT * FROM `sitevipdb`.`game_shares_buy_list` WHERE price>='2.0′ ORDER BY tran_id DESC LIMIT 10; 。。。。。 10 rows in set (7.06 sec) root@127.0.0.1 : sitevipdb 09:36:00> SELECT * FROM `sitevipdb`.`game_shares_buy_list` force index(ind_game_shares_buy_list_price) WHERE price>='2.0′ ORDER BY tran_id DESC LIMIT 10; 。。。。 10 rows in set (1.01 sec) 可以看到如果强制走索引,时间已经明显下降了,但是还是有些慢,能不能在快一点?其实我们需要扫描的记录只有10条,但查询在取得这10条记录的时候需要扫描大量无效的记录 怎么降低这个数据:其实只要改写一下sql就可以,我们先从索引中得到满足条件的10个id,在回表进行关联: root@127.0.0.1 : sitevipdb 09:44:45> select * from game_shares_buy_list t1,-> ( SELECT tran_id FROM sitevipdb.game_shares_buy_list WHERE price>='2.0′ ORDER BY tran_id DESC LIMIT 10) t2 -> where t1.tran_id=t2.tran_id; 10 rows in set (0.00 sec) 可以看到执行时间已经不在秒级别了,和客户电话沟通后,很愿意这样改写sql。 ―这里看到是order by tran_id是要额外排序的,索引也可以这样来建立消除排序(tran_id,price)这样可以消除排序,同时可以利用order by desc/asc +limit M,N的优化。 优化点二: CREATE TABLE `game_session` ( `session_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT,`session_expires` int(10) unsigned NOT NULL DEFAULT '0′,`client_ip` varchar(16) DEFAULT NULL,`session_data` text,……………………. PRIMARY KEY (`session_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 查询为select `session_data`,`session_expires` from `game_session` where session_id='xxx'出现大量等待情况 CREATE TABLE `game_session` ( id int auto_increment,`session_id` varchar(30) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT,`session_data` varchar(200),PRIMARY KEY (id),key ind_session_id(session_id,session_data,session_expires) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 小结:
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |