select * from T where k between 3 and 5,需执行几次树搜索,扫描多少行?2、2
mysql> create table T (
ID int primary key,
k int NOT NULL DEFAULT 0,
s varchar(16) NOT NULL DEFAULT '',
index k(k))
engine=InnoDB;
insert into T values(100,1, 'aa'),(200,2,'bb'),(300,3,'cc'),(500,5,'ee'),(600,6,'ff'),(700,7,'gg');
执行流程:
1. k 索引树找 k=3 ,取得 ID = 300;
2. 再到 ID 索引树查到 ID=300 对应 R3;
3. 在 k 索引树取下一个值 k=5,取得 ID=500;
4. 再回到 ID 索引树查到 ID=500 对应的 R4;
5. k 索引树取下一个值 k=6,不满足条件,循环结束。
回到主键索引树搜索的过程,为回表(步骤 2 和 4)。读了 k 索引树的 3 条记录( 1、3 和 5)
查询结果需数据只主键索引上有,不得不回表。如何优化、避免回表过程?
一、覆盖索引
select ID from T where k between 3 and 5,查 ID 的值(已经在 k 索引树上),不需回表。也就是说,在这个查询里面,索引 k “覆盖”查询需求,为覆盖索引。
减少树搜索次数,也是优化手段。
索引 k 上读了三个记录,R3~R5,但对于 MySQL 的 Server 层来说,找引擎拿两条记录,扫描行数是 2。
假设这个市民表的定义是这样的:
CREATE TABLE `tuser` (
`id` int(11) NOT NULL,
`id_card` varchar(32) DEFAULT NULL,
`name` varchar(32) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`ismale` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `id_card` (`id_card`),
KEY `name_age` (`name`,`age`)
) ENGINE=InnoDB
高频请求,根据身份证号查询姓名,联合索引就有意义。覆盖索引,不需回表查整行记录
索引维护有代价。建立冗余索引来支持覆盖索引时就权衡考虑
二、最左前缀原则
按身份证号去查地址?怎么做?每种查询都设计一个索引,索引是不是太多
B+ 树这种索引结构,用索引“最左前缀”定位记录。(name,age)这个联合索引来分析。
name= “张三”时,快速定位 ID4+向后遍历 get所有结果。
name like ‘张 %’"时,也能够用这个索引,查找 ID3,向后遍历,直到不满足
满足最左前缀,可利用索引加速检索。可以是联合索引最左 N 个字段,也可字符串索引最左 M 个字符。
在建立联合索引时,如何安排索引内字段顺序
索引复用能力。有了 (a,b) 联合索引后,不需要单独在 a 上建索引。第一原则是,调整顺序,可少维护
开头问题,并用索引支持“根据身份证号查询地址”,需同时维护 (a,b)、(b) 这两个索引。
考虑空间,name 比 age 字段大 ,创建(name,age) 联合索引和(age) 单字段索引。
三、索引下推
不符合最左前缀部分,会怎样?
联合索引(name, age)“名字第一个字是张,年龄10 岁男孩”
mysql> select * from tuser where name like '张%' and age=10 and ismale=1;
搜索索引树时, “张”找到 ID3。比全表扫描好。然后判断其他条件
索引下推优化(index condition pushdown), 索引遍历过程中,对索引包含字段先判断,直接过滤掉不满足条件的记录,减少回表次数。
虚线箭头表示回表一次。
图 3 :在 (name,age) 索引里面我特意去掉了 age 的值,这个过程 InnoDB 并不会去看 age 的值,按顺序把“name 第一个字是’张’”的记录一条条取出来回表。回表 4 次。
图 4 跟图 3 的区别:InnoDB 在 (name,age) 索引内部判断 age 是否等于 10,不等跳过。回表 2 次。
小结
覆盖索引、前缀索引、索引下推。满足语句需求情况下,少访问资源。问题:
主键索引也可用多个字段。
CREATE TABLE `geek` (
`a` int(11) NOT NULL,
`b` int(11) NOT NULL,
`c` int(11) NOT NULL,
`d` int(11) NOT NULL,
PRIMARY KEY (`a`,`b`),
KEY `c` (`c`),
KEY `ca` (`c`,`a`),
KEY `cb` (`c`,`b`)
) ENGINE=InnoDB;
历史原因,需 a、b 做联合主键。
既然主键包含 a、b 这两个字段, c 上创建索引,已经包含了三个字段,为什么要创建“ca”“cb”这两个索引?同事说因为业务里面这样语句:
select * from geek where c=N order by a limit 1;
select * from geek where c=N order by b limit 1;
问题:解释对吗,为了这两个查询模式,这两个索引是否都是必须的?为什么呢?
答:最左匹配原则
–a-|–b--|–c--|–d--
1 2 3 d
1 3 2 d
1 4 3 d
2 1 3 d
2 2 2 d
2 3 4 d
主键 a,b: 聚簇索引组织顺序相当于 order by a,b ,先按 a ,再 b 排序,c 无序。
索引 ca 先 c ,再 a 排序,记录主键
–c--|–a--|–主键部分b-- (注意,这里不是 ab,而是只有 b)
2 1 3
2 2 2
3 1 2
3 1 4
3 2 1
4 2 3
跟索引 c 的数据一样
索引 cb 的组织是先按 c 排序,在按 b 排序,同时记录主键
–c--|–b--|–主键部分a-- (同上)
2 2 2
2 3 1
3 1 2
3 2 1
3 4 1
4 3 2
ca 去,cb 保留
评论1
线上表, 记录日志, 定期删除数据. 表实际10G, 索引30G. 阿里云上占了40G. 真金白银啊.
后来了解到是 InnoDB 引擎删除表记录,索引还在, 未释放。重新建表才能重建索引.
评论2
不影响排序结果的情况下,取出主键后,回表前,对获取到主键排序
不论是否使用 Multi-Range Read (MRR) 策略,SQL语句写法不变