- 查询优化
- 3.关联查询优化
- 4.子查询优化
- 2.用in 还是exists
- https://blog.csdn.net/weixin_41979002/article/details/118730611">https://blog.csdn.net/weixin_41979002/article/details/118730611
- 5.排序分组优化
- 6. 最后使用索引的手段:覆盖索引
- 7.分页查询的优化—-limit
- 8.尽量不要使用 distinct 关键字去重:优化
查询优化
1.批量数据脚本
1.往表里插入50W数据
1.建表
CREATE TABLE `dept` (`id` INT(11) NOT NULL AUTO_INCREMENT,`deptName` VARCHAR(30) DEFAULT NULL,`address` VARCHAR(40) DEFAULT NULL,ceo INT NULL ,PRIMARY KEY (`id`)) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;CREATE TABLE `emp` (`id` INT(11) NOT NULL AUTO_INCREMENT,`empno` INT NOT NULL ,`name` VARCHAR(20) DEFAULT NULL,`age` INT(3) DEFAULT NULL,`deptId` INT(11) DEFAULT NULL,PRIMARY KEY (`id`)#CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
2.设置参数log_bin_trust_function_creators
创建函数,假如报错:This function has none of DETERMINISTIC……
由于开启过慢查询日志,因为我们开启了 bin-log, 我们就必须为我们的function指定一个参数。
Mysql开启log-bin日志
- 1、检查是否开启
- 检查是否开启show global variables like ‘%log_bin%’; log_bin 为 on 则已开启,为 off则未开启。
- 查询所有日志文件show binary logs;
- 查询最近日志文件show master status;
```plsql show variables like ‘log_bin_trust_function_creators’;
set global log_bin_trust_function_creators=1;
**这样添加了参数以后,如果mysqld重启,上述参数又会消失,永久方法:**
```plsql
windows下my.ini[mysqld]加上log_bin_trust_function_creators=1
linux下 /etc/my.cnf下my.cnf[mysqld]加上log_bin_trust_function_creators=1
3.创建函数,保证每条数据都不同
随机产生字符串
create function rand_string(n int)returns varchar(255)
begin
declare char_str varchar(100) default 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
declare return_str varchar(255) default '';
declare i int default 0;
while i<n do
set return_str=concat(return_str,substring(char_str,floor(1+rand()*52),1));
set i=i+1;
end while;
return return_str;
end
select rand_string(5);
随机产生部门编号
用于随机产生多少到多少的编号
create function rand_num(form_num int,to_num int)returns int(11)
begin
declare i int default 0;
set i=floor(form_num+rand()*(to_num-form_num+1));
return i;
end
select rand_num(1,5);
drop function rand_num;
4.创建存储过程
show variables like ‘autocommit’; 是否开启自动提交
set autocommit=0;关闭自动提交
select @@autocommit;
1.创建往emp表中插入数据的存储过程
delimiter $$
create procedure insert_emp(start_num int,max_num int)
begin
declare i int default 0;
set autocommit =0;
repeat
set i=i+1;
insert into emp(empno,name,age,deptid)values((start_num+i),rand_string(6),rand_num(30,50),rand_num(1,10000));
until i=max_num
end repeat;
commit;
end $$
delimiter ;
call insert_emp(1,5);
select * from emp;
truncate table emp;
drop procedure insert_emp;
2.创建往dept表中插入数据的存储过程
执行存储过程,往dept表添加随机数据
delimiter $$
create procedure insert_dept(in max_num int)
begin
declare i int default 0;
set autocommit=0;
repeat
set i=i+1;
insert into dept(deptName,address,ceo)values(rand_string(8),rand_string(10),rand_num(1, 15000));
until i=max_num
end repeat;
commit;
end $$
delimiter;
call insert_dept(5);
select * from dept;
truncate table dept;
drop PROCEDURE insert_dept;
3.调用存储过程
**dept**
执行存储过程,往dept表添加1万条数据
delimiter;
call insert_dept(10000);
select count(1) from dept;
<br />**emp**
执行存储过程,往emp表添加50万条数据
delimiter;
call insert_emp(100000,500000);
select count(1) from emp;
2.批量删除某个表上的所有索引
1.存储过程
DELIMITER $$
CREATE PROCEDURE `proc_drop_index`(dbname VARCHAR(200),tablename VARCHAR(200))
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE ct INT DEFAULT 0;
DECLARE _index VARCHAR(200) DEFAULT '';
DECLARE _cur CURSOR FOR SELECT index_name FROM information_schema.STATISTICS WHERE table_schema=dbname AND table_name=tablename AND seq_in_index=1 AND index_name <>'PRIMARY' ;
DECLARE CONTINUE HANDLER FOR NOT FOUND set done=2 ;
OPEN _cur;
FETCH _cur INTO _index;
WHILE _index<>'' DO
SET @str = CONCAT("drop index ",_index," on ",tablename );
PREPARE sql_str FROM @str ;
EXECUTE sql_str;
DEALLOCATE PREPARE sql_str;
SET _index='';
FETCH _cur INTO _index;
END WHILE;
CLOSE _cur;
END$$
2.执行存储过程
CALL proc_drop_index("dbname","tablename");
2.单表使用索引及常见索引失效
1.案例(索引失效)
1.全值匹配我最爱
系统中经常出现的sql语句如下:
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4 AND emp.name = 'abcd'
索引应该如何建立 ?
**建立索引**
create index idx_age_deptid_name on emp(age,deptid,name);
2.最佳左前缀法则
如果系统经常出现的sql如下:
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.name = 'abcd'
或者
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.deptid=1 AND emp.name = 'abcd'
那原来的idx_age_deptid_name 还能否正常使用?
**如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列**。
1.中间少了一项,与索引不一致
2.最左的一项缺少
3. 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
这两条sql哪种写法更好
create index name_idex on emp(name);
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name LIKE 'abc%'
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE LEFT(emp.name,3) = 'abc'
1.第一种:
2.第二种
4.存储引擎不能使用索引中范围条件右边的列
如果系统经常出现的sql如下:
ELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.deptId>20 AND emp.name = 'abc' ;
那么索引 idx_age_deptid_name这个索引还能正常使用么?

如果这种sql 出现较多,应该建立:
create index idx_age_name_deptid on emp(age,name,deptid);
drop index idx_age_name_deptid on emp;
5.mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描
CREATE INDEX idx_name ON emp(name);
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name = 'abc';
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name <> 'abc';
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name != 'abc';
drop idx_name on emp;
6. is not null 也无法使用索引,但是is null是可以使用索引的
UPDATE emp SET age =NULL WHERE id=123456;
#下列哪个sql语句可以用到索引
EXPLAIN SELECT * FROM emp WHERE age IS NULL;
EXPLAIN SELECT * FROM emp WHERE age IS NOT NULL;
7.like以通配符开头(‘%abc…’)mysql索引失效会变成全表扫描的操作
create index age_name_idex on emp(age,name);
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.name like 'abc%';
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.name like '%abc%';
drop index age_name_idex on emp;
8. 字符串不加单引号索引失效
CREATE INDEX idx_name ON emp(name);
drop idx_name on emp;
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name = '123';
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name = 123;
9.小总结
假设index(a,b,c)
| Where语句 | 索引是否被使用 |
|---|---|
| where a = 3 | Y,使用到a |
| where a = 3 and b = 5 | Y,使用到a,b |
| where a = 3 and b = 5 and c = 4 | Y,使用到a,b,c |
| where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 | N |
| where a = 3 and c = 5 | 使用到a, 但是c不可以,b中间断了 |
| where a = 3 and b > 4 and c = 5 | 使用到a和b, c不能用在范围之后,b断了 |
| where a is null and b is not null | is null 支持索引 但是is not null 不支持,所以 a 可以使用索引,但是 b不可以使用 |
| where a <> 3 | 不能使用索引 |
| where abs(a) =3 | 不能使用 索引 |
| where a = 3 and b like ‘kk%’ and c = 4 | Y,使用到a,b,c |
| where a = 3 and b like ‘%kk’ and c = 4 | Y,只用到a |
| where a = 3 and b like ‘%kk%’ and c = 4 | Y,只用到a |
| where a = 3 and b like ‘k%kk%’ and c = 4 | Y,使用到a,b,c |
2.一般性建议
1.对于单键索引,尽量选择针对当前query过滤性更好的索引
2.在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。
3.在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引
4.在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面
5.书写sql语句时,尽量避免造成索引失效的情况
3.关联查询优化
1.建表SQL
CREATE TABLE IF NOT EXISTS `class` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `book` (
`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`bookid`)
);
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
select * from class;
#####################
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
select * from book;
2. 案例
1.下面开始explain分析
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
结论:type 有All
2.添加索引优化
ALTER TABLE `book` ADD INDEX Y ( `card`);
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
可以看到第二行的 type 变为了 ref,rows 也变成了优化比较明显

这是由左连接特性决定的。LEFT JOIN 条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要建立索引
# 删除旧索引 + 新建 + 第3次explain
DROP INDEX Y ON book;
ALTER TABLE class ADD INDEX X (card);
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
3.建议
1、保证被驱动表的join字段已经被索引
2、left join 时,选择小表作为驱动表,大表作为被驱动表。
3、inner join 时,mysql会自己帮你把小结果集的表选为驱动表。
4、子查询尽量不要放在被驱动表,有可能使用不到索引。
5、能够直接多表关联的尽量直接关联,不用子查询。
4.子查询优化
1.尽量不要使用not in 或者 not exists
取所有不为掌门人的员工,按年龄分组 ,每个年龄段多少人
SELECT SQL_NO_CACHE age,count(*) FROM emp a WHERE id NOT IN(SELECT ceo FROM dept b2 WHERE ceo IS NOT NULL) group by age having count(*)<10000
1. 2.用left outer join on xxx is null 替代
2.用in 还是exists
https://blog.csdn.net/weixin_41979002/article/details/118730611
1.有索引的情况下 用 inner join 是最好的 其次是 in ,exists最糟糕
2.无索引的情况下用
小表驱动大表 因为join 方式需要distinct ,没有索引distinct消耗性能较大
所以 exists性能最佳 in其次 join性能最差?
3.无索引的情况下大表驱动小表
in 和 exists 的性能应该是接近的 都比较糟糕 exists稍微好一点 超不过5% 但是inner join 优于使用了 join buffer 所以快很多 如果left join 则最慢
5.排序分组优化
1.case
create index idx_age_deptid_name on emp (age,deptid,name)
以下 是否能使用到索引,能否去掉using filesort
1、explain select SQL_NO_CACHE * from emp order by age,deptid;
2、explain select SQL_NO_CACHE * from emp order by age,deptid limit 10;
#无过滤 不索引
3、explain select * from emp where age=45 order by deptid;
4、explain select * from emp where age=45 order by deptid,name;
5、explain select * from emp where age=45 order by deptid,empno;
6、explain select * from emp where age=45 order by name,deptid;
7、explain select * from emp where deptid=45 order by age;
#顺序错,必排序
8、 explain select * from emp where age=45 order by deptid desc, name desc ;
9、 explain select * from emp where age=45 order by deptid asc, name desc ;
#方向反 必排序
2. ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序
3.索引的选择
1.执行案例前先清除emp上的索引,只留主键
call proc_drop_index(“test”,”emp”);
2.索引的选择
查询 年龄为30岁的,且员工编号小于101000的用户,按用户名称排序
SELECT SQL_NO_CACHE * FROM emp WHERE age =30 AND empno <101000 ORDER BY NAME ;
explain SELECT SQL_NO_CACHE * FROM emp WHERE age =30 AND empno <101000 ORDER BY NAME ;

结论:很显然,type 是 ALL,即最坏的情况。Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的。
1.开始优化:
思路: 尽量让where的过滤条件和排序使用上索引
但是一共两个字段(deptno,empno)上有过滤条件,一个字段(ename)有索引
1、我们建一个三个字段的组合索引可否?
CREATE INDEX idx_age_empno_name ON emp(age,empno,NAME);
我们发现using filesort 依然存在,所以name 并没有用到索引。原因是因为empno是一个范围过滤,所以索引后面的字段不会再使用索引了!
所以我们建一个3值索引是没有意义的 那么我们先删掉这个索引,
DROP INDEX idx_age_empno_name ON emp
为了去掉filesort我们可以把索引建成
CREATE INDEX idx_age_name ON emp(age,NAME);
也就是说empno 和name这个两个字段我只能二选其一。这样我们优化掉了 using filesort。
优化前:
优化后:
2.但是 如果我们选择那个范围过滤,而放弃排序上的索引呢
1.建立
DROP INDEX idx_age_name ON emp
create index idx_age_eno on emp(age,empno);

果然出现了filesort,而且type还是range光看字面其实并不美好。
SELECT SQL_NO_CACHE * FROM emp WHERE age =30 AND empno <101000 ORDER BY NAME ;


结果竟然有 filesort的 sql 运行速度,超过了已经优化掉 filesort的 sql ,而且快了好多倍。何故?
原因是所有的排序都是在条件过滤之后才执行的,所以如果条件过滤了大部分数据的话,几百几千条数据进行排序其实并不是很消耗性能,即使索引优化了排序但实际提升性能很有限。 相对的 empno<101000 这个条件如果没有用到索引的话,要对几万条的数据进行扫描,这是非常消耗性能的,所以索引放在这个字段上性价比最高,是最优选择
2.结论:
当范围条件和group by 或者 order by 的字段出现二选一时 ,优先观察条件字段的过滤数量,如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。反之,亦然
4.如果不在索引列上,filesort有两种算法: mysql就要启动双路排序和单路排序
1.双路排序
1 .MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据, 读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出<br /> 2. 从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。<br /> 3.取一批数据,要对磁盘进行了两次扫描,众所周知,I\O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。
2.单路排序
从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出, 它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间, 因为它把每一行都保存在内存中了。
3。结论及引申出的问题
由于单路是后出的,总体而言好过双路 但是**用单路有问题**<br />**在sort_buffer中,方法B比方法A要多占用很多空间,因为方法B是把所有字段都取出, 所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取取sort_buffer容量大小,再排……从而多次I/O。本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。**
4.优化策略
1.增大sort_buffer_size参数的设置
2.增大max_length_for_sort_data参数的设置
3.减少select 后面的查询的字段。
4.Why
1. Order by时select * 是一个大忌只Query需要的字段, 这点非常重要。在这里的影响是:
1.1 当Query的字段大小总和小于max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法——单路排序, 否则用老算法——多路排序。
1.2 两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size。
2. 尝试提高 sort_buffer_size
不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的 1M-8M之间调整
3. 尝试提高 max_length_for_sort_data
提高这个参数, 会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率. 1024-8192之间调整
5.GROUP BY关键字优化
**group by 使用索引的原则几乎跟order by一致 ,唯一区别是groupby 即使没有过滤条件用到索引,也可以直接使用索引。**
1.group by实质是先排序后进行分组,遵照索引建的最佳左前缀
2.当无法使用索引列,增大max_length_for_sort_data参数的设置+增大sort_buffer_size参数的设置
3.where高于having,能写在where限定的条件就不要去having限定了。
6. 最后使用索引的手段:覆盖索引
1.什么是覆盖索引?
简单说就是,select 到 from 之间查询的列 <=使用的索引列+主键

explain select * from emp where name like ‘%abc’;

使用覆盖索引前
使用覆盖索引后
7.分页查询的优化—-limit
EXPLAIN SELECT SQL_NO_CACHE FROM emp ORDER BY deptno LIMIT 10000,40

那我们就给deptno这个字段加上索引吧。
然并卵。
优化: 先利用覆盖索引把要取的数据行的主键取到,然后再用这个主键列与数据表做关联:(查询的数据量小了后)
EXPLAIN SELECT SQL_NO_CACHE FROM emp INNER JOIN (SELECT id FROM emp e ORDER BY deptno LIMIT 10000,40) a ON a.id=emp.id
最后比较一下查询速度:
优化前:
优化后:
实践证明:
①、order by 后的字段(XXX)有索引
②、sql 中有 limit 时,
当 select id 或 XXX字段索引包含字段时 ,显示 using index
当 select 后的字段含有 bouder by 字段索引不包含的字段时,将显示 using filesort
8.尽量不要使用 distinct 关键字去重:优化
t_mall_sku 表
id shp_id kcdz
——— ——— ——————————
3 1 北京市昌平区
4 1 北京市昌平区
5 5 北京市昌平区
6 3 重庆
8 8 天津
例子:select kcdz form t_mall_sku where id in( 3,4,5,6,8 ) 将产生重复数据,
使用 distinct 关键字去重消耗性能:select distinct kcdz form t_mall_sku where id in( 3,4,5,6,8 )
优化: select kcdz form t_mall_sku where id in( 3,4,5,6,8 ) group by kcdz 能够利用到索引





