SQL优化:执行计划出错之重复索引干扰
Updated:
本篇为执行计划错误系列第二篇,第一篇地址
背景
MySQL版本
社区版: MySQL 5.6.27 InnoDB
表结构
| test_table | CREATE TABLE
test_table
(
TagId
int(11) NOT NULL AUTO_INCREMENT COMMENT ‘’,
TagType
int(11) DEFAULT NULL COMMENT ‘’,
SubType
int(11) DEFAULT NULL COMMENT ‘’,
CommId
int(11) NOT NULL DEFAULT ‘0’ COMMENT ‘’,
TagFlag
int(11) NOT NULL DEFAULT ‘0’ COMMENT ‘’,
TagName
varchar(255) DEFAULT NULL COMMENT ‘’,
OrderId
int(11) DEFAULT ‘0’ COMMENT ‘’,
Unum
int(10) NOT NULL DEFAULT ‘0’ COMMENT ‘’,
IsBest
int(11) NOT NULL DEFAULT ‘0’ COMMENT ‘’,
BrokerId
int(11) NOT NULL DEFAULT ‘0’ COMMENT ‘’,
AddDate
int(11) DEFAULT NULL COMMENT ‘’,
UpdateDate
int(11) DEFAULT NULL COMMENT ‘’,
updatetime
timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
tmpnum
int(10) DEFAULT ‘0’ COMMENT ‘’,
cityid
int(11) DEFAULT ‘0’ COMMENT ‘’,
PRIMARY KEY (TagId
),
KEYidx_4
(IsBest
,TagFlag
,CommId
),
KEYidx_1
(TagType
,TagName
,CommId
),
KEYidxnew
(UpdateDate
),
KEYidx_lc
(TagType
,TagName
,TagId
)
KEYidx_lc_2
(TagName
,TagType
,TagId
),
) ENGINE=InnoDB AUTO_INCREMENT=19387169 DEFAULT CHARSET=utf8 |
index 选择性
|
|
问题SQL
select * from test_db.test_table where
TagType
= ‘1’ andTagName
= ‘采光充足’ order by TagId limit 1;
问题再哪里?
- 执行计划
root:test_db> explain select * from test_db.test_table where
TagType
= 1 andTagName
= ‘采光充足’ order by TagId limit 1;
+——+——————-+————————-+———-+———————-+————-+————-+———+———+——————-+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+——+——————-+————————-+———-+———————-+————-+————-+———+———+——————-+
| 1 | SIMPLE | test_table | index | idx_1,idx_lc,idx_lc_2 | PRIMARY | 4 | NULL | 386 | Using where |
+——+——————-+————————-+———-+———————-+————-+————-+———+———+——————-+
1 row in set (0.00 sec)Time: 160313 0:07:16
User@Host: [] @ [] Id: 7123914939
Query_time: 0.246356 Lock_time: 0.000036 Rows_sent: 1 Rows_examined: 470135
SET timestamp=1457798836;
selectTagId
,TagType
,SubType
,CommId
,TagFlag
,TagName
,OrderId
,Unum
,IsBest
,BrokerId
,AddDate
,UpdateDate
,updatetime
,tmpnum
,cityid
fromtest_table
whereTagType
= 1 andTagName
= ‘采光充足’ order byTagId
ASC limit 1 ;
- 痛点分析
1)primary , type=index,表示全索引扫描。
2) 按照索引原理,这样的SQL语句应该使用的正确的索引是:idx_lc, type=ref。
root:test_db> explain select * from test_db.test_table force index(idx_lc) where TagType
= 1 and TagName
= ‘采光充足’ order by TagId limit 1;
|
|
3)到这里,肯定大部分人问,既然force index可以解决问题,为啥不让开发修改SQL为force index呢?
a) 能不让开发修改,就不让开发修改,这样成本最低。
b)况且,这根本就不是SQL语句本身的问题,为什么要修改SQL呢?
c)如果以后不小心删掉了这个索引,那么force index的语句就会报错,会导致业务中断。
以上种种原因,force index 始终不是最终的积极方案。
那么问题来了
使用idx_lc代价最小,速度最快。 为什么MySQL不用呢?
继续分析
- 如果观察仔细的小伙伴们会发现,以上的索引有很多是重复的
PRIMARY KEY (
TagId
),
KEYidx_4
(IsBest
,TagFlag
,CommId
),
KEYidx_1
(TagType
,TagName
,CommId
),
KEYidxnew
(UpdateDate
),
KEYidx_lc
(TagType
,TagName
,TagId
)
KEYidx_lc_2
(TagName
,TagType
,TagId
),
- possibles key 的选择问题
为什么在 idx_1,idx_lc,idx_lc_2 中 却选择的 primary key 呢?
- 我试着删掉一个索引试试看
root:test_db> alter table test_table drop index idx_1;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
root:test_db> explain select * from test_db.test_table force index(idx_lc) whereTagType
= 1 andTagName
= ‘采光充足’ order by TagId limit 1;
+——+——————-+————————-+———+———————-+————+————-+——————-+———+——————————————————+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+——+——————-+————————-+———+———————-+————+————-+——————-+———+——————————————————+
| 1 | SIMPLE | test_table | ref | idx_lc,idx_lc_2 | idx_lc | 773 | const,const | 2930 | Using index condition; Using where |
+——+——————-+————————-+———+———————-+————+————-+——————-+———+——————————————————+
1 row in set (0.00 sec)
这样,执行语句奇迹般的好了。让我开始对index 的顺序问题产生怀疑
ok,我在重建idx_1索引
PRIMARY KEY (
TagId
),
KEYidx_4
(IsBest
,TagFlag
,CommId
),
KEYidxnew
(UpdateDate
),
KEYidx_lc
(TagType
,TagName
,TagId
)
KEYidx_lc_2
(TagName
,TagType
,TagId
),
KEYidx_1
(TagType
,TagName
,CommId
),
|
|
- 我再将idx_lc 删掉后再重建呢?
PRIMARY KEY (
TagId
),
KEYidx_4
(IsBest
,TagFlag
,CommId
),
KEYidxnew
(UpdateDate
),
KEYidx_lc_2
(TagType
,TagName
,TagId
)
KEYidx_1
(TagType
,TagName
,CommId
),
KEYidx_lc
(TagType
,TagName
,TagId
)
|
|
最后的优化建议:
|
|
疑问
MySQL不至于这么傻吧?应该不是根据RBO哇,说好的CBO呢?
所以,我又在test库中创新创建了一张表,表结构和原来一样,数据完全导入到过来
|
|
最后总结
- 从上述测试来看,MySQL执行计划应该是 CBO + CRO的结合体 ? 待确认
- 一般情况下,MySQL可以很好的选择执行计划,但是收到重复索引,数据分布等的干扰后,可能不准确
- 我们能做的就是
- 等待官方优化器的改进
- 尽量避开干扰项,如:主动删除重复性索引等
详细的执行代价评估
- 错误的选择
|
|
- 正确的选择
|
|