話不多說,上表:
factor_status_log_to_reason(3億條數據):
CREATE TABLE `factor_status_log_to_reason` (
`fsl_id` int(11) unsigned NOT NULL,
`reason_id` int(11) unsigned NOT NULL,
PRIMARY KEY (`fsl_id`,`reason_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
factor_status_log(2億條數據):
CREATE TABLE `factor_status_log` (
`fsl_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`object_key` varchar(255) NOT NULL,
`operator_name` varchar(32) NOT NULL,
`operation_id` int(11) unsigned NOT NULL,
`products_id` int(11) unsigned NOT NULL,
`mc_id` int(11) unsigned NOT NULL,
`object_status` int(11) DEFAULT NULL,
`date_added` timestamp NOT NULL DEFAULT '1999-09-09 00:00:00',
PRIMARY KEY (`fsl_id`),
KEY `idx_objk` (`object_key`),
KEY `idx_pm` (`products_id`,`mc_id`),
KEY `idx_date` (`date_added`)
) ENGINE=InnoDB AUTO_INCREMENT=343169419 DEFAULT CHARSET=utf8
不要在意爲啥一個表這麼多數據,因爲他就是有這麼多數據,沒有分庫分表
需求:找到reason_id爲64,67的產品id以及相關數據。
第一版sql:
select * from factor_status_log fsl join factor_status_log_to_reason fsltr on fsltr.fsl_id = fsl.fsl_id and fsltr.reason_id in (67,64) limit 10;
執行之後發現巨慢,用explain查看執行計劃如下:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | fsl | ALL | PRIMARY | NULL | NULL | NULL | 207636393 | |
1 | SIMPLE | fsltr | ref | PRIMARY | PRIMARY | 4 | products_center_v1.fsl.fsl_id | 1 | Using where; Using index |
不理解爲啥一個表沒有走索引,過濾條件都是加索引的呀。
接下來進入了短暫的捋原理階段:
兩個錶鏈接的原理是這樣的,如果是外連接,那麼可以自己選定一張表作爲驅動表,如果是內連接,MySQL會選定一張表作爲驅動表。比如以上 我寫的sql是內連接,那個表是驅動表呢?看explain第一條的表fsl就是驅動表。
選定驅動表之後,會對驅動表進行單表掃描,掃描出結果。比如掃描出兩條數據fsl_id = 1,2。
然後才能利用到關聯條件fsltr.fsl_id = fsl.fsl_id,對被驅動表進行兩次查找,也就是fsltr.fsl_id = 1,fsltr.fsl_id = 2。如果驅動表找出一萬條數據,那麼被驅動表就需要查找一萬次。有沒有感覺到其實這就像是一個嵌套循環?對滴,這種查找就叫循環嵌套查詢。
原理知道了,爲啥咱寫的說sql慢呢?根本原因就是在MySQL首先對驅動表factor_status_log進行單表查詢的時候,並沒有對其用索引條件進行過濾,就相當於
select * from factor_status_log;
並不會走索引,所以是全表掃描。
優化:
select * from factor_status_log_to_reason fsltr ,factor_status_log fsl where fsl.date_added < '2019-01-07 00:00:00' and fsl.date_added > '2018-06-07 00:00:00' and fsltr.fsl_id = fsl.fsl_id and fsltr.reason_id in (67,64) limit 10;
目的是對驅動表用索引date_added進行過濾一下。執行計劃如下:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | fsl | range | PRIMARY,idx_date | idx_date | 4 | NULL | 61972696 | Using where |
1 | SIMPLE | fsltr | ref | PRIMARY | PRIMARY | 4 | products_center_v1.fsl.fsl_id | 1 | Using where; Using index |
可以看到單表掃描是range,並且走了索引idx_date。
可以在幾秒之內出結果了。