第21章:索引优化与查询优化

一、索引优化与查询优化

1.什么情况下要进行数据库调优

①索引失效,没有充分利用到索引---索引建立

②关联查询太多join---SQL优化

③服务器调优和各个参数的设置---调整my.cnf

④数据过多---分库分表

2.SQL优化的技术

①物理查询优化:通过索引和表连接方式进行优化

②逻辑查询优化:通过SQL的等价变化提升查询效率

二、关联查询优化

1.左外连接

EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;

驱动表(左表A):type

被驱动表(右表B):book

①没有索引

 type的值是all,进行的全表扫描,效率低。

②给被驱动表book添加索引

alter table book add index Y (card);
或
create index Y on book(card);

 发现book的type为ref,类型表示 MySQL 在查询时使用了非唯一索引,通常是在连接操作中使用。避免全表扫描效率高。

给驱动表type添加索引,删除book的索引Y

create index X on type(card);
drop index Y on book;

【左外连接优化分析】

左外连接在查询时,左表的每一条数据跟右表的数据进行挨个匹配,左表建立索引意义不大。

左连接时对右表on的字段建立索引,提高查询效率。

 2.内连接

EXPLAIN SELECT SQL_NO_CACHE * FROM `type` INNER JOIN book ON type.card = book.card;

 

①添加book索引

create index Y on book(card);

 发现book的type为ref,类型表示 MySQL 在查询时使用了非唯一索引,通常是在连接操作中使用。避免全表扫描效率高。

②添加type索引

create index X on type(card);

 ③删除book索引

【内连接优化分析】

在内连接中,如果此时有一个表的连接条件中的字段有索引,那么这个表会作为被驱动表。

如果两个表的连接字段都有索引。数据量小的是驱动表,数据量大的是被驱动表。小表驱动大表

所以给数据量大的表创建索引就能提高查询效率

三、JOIN语句原理

1.判断驱动表和非驱动表

①左外连接:左边是驱动表,右边是被驱动表

②右外连接:右边是驱动表,左边是被驱动表

③内连接inner order:mysql会选择数据量小的是驱动表,数据量大的是非驱动表

④通过EXPLAIN查看SQL的执行计划,第一行的表是驱动表

2.join语句的相关算法

①Simple Nested-Loop Join简单嵌套循环连接SNLJ

从表A取出1条数据,遍历表B。匹配结束清除内存,然后再加载A的1条数据,进行匹配。效率低。

②Index Nested-Loop Join索引嵌套循环连接INLJ

主要思想是减少内层数据的匹配次数,所以是让被驱动表必须有索引才行。效率高

③Block Nested-Loop Join 块嵌套循环连接BNLJ(MySQL8.0取消了)

如果没有索引,将驱动表join相关部分的列缓存到join buffer中,然后全表扫描被驱动表。被驱动表的记录和块记录进行匹配。跟一条一条记录比较来说能减少IO的次数。

 

【小结】

①整体的效率比较:INLJ>BNLJ>SNLJ

②小表驱动大表,本质减少外层循环的数据数量。为被驱动表匹配的条件增加索引,减少内层循环数量。

③增大join buffer size的大小,一次性缓存的数据多,扫表的次数越少。减少驱动表不必要的查询字段,join buffer缓存的数据越多

四、排序优化order by

1.在where条件字段上加索引,但是为什么在order by 字段还要添加索引?

①在where子句添加索引是避免全表扫描,在order by子句添加索引是避免使用FileSort排序(占CPU,效率低)。

②尽量使用索引完成order by排序。如果where和order by后面相同的列使用单列索引。如果不同使用联合索引。

③无法使用索引时,需要对FileSort方式调优

2.排序时使用索引的情况

创建索引:index a_b_c(a,b,c)

①order by使用索引最前缀

order by a

order by a,b

order by a,b,c

order by a desc,b desc,c desc

②where 使用的索引最左前缀为常量,order by能使用索引

where a = 常量 order by b,c

where a = 常量 and b = 常量 order by c

where a = 常量 and b > 常量 order by b,c

③不能使用索引

order by a asc,b desc,c desc 排序不一致

where g = 常量 order by b,c 丢失a索引

where a = 常量 order by c 丢失b索引

where a = 常量 order by a,d d不是索引

WHERE a in (...) ORDER BY b,c 对于排序来说,多个相等条件也是范围查询

3.案例实战

order by子句尽量使用索引方式排序,避免使用filesort排序

场景:查询年龄是30岁,且学生编号小于101000学生,按照用户名称排序

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND stuno <101000 ORDER BY  NAME ;

 type是all,进行全表扫描最坏的情况。Extra出现了Using filesort。要进行优化

①方案一:创建索引age_name,没有使用filesort

CREATE INDEX idx_age_name ON student(age,NAME);

②方案二:where和order by 字段使用索引,使用filesort

CREATE INDEX idx_age_stuno_name ON student (age,stuno,NAME);

速度快

五、分组优化group by

1.group by 使用原则跟order by一致,没有where过滤条件也可以直接用索引。

2.group by先排序再分组,遵循索引建立的最佳左前缀原则

3.where效率高于having,能写在where条件里就不写在having中

4.减少使用order by,group by,distinct语句费CPU。

5. 用order by,group by,distinct语句,where条件过滤出的结果在1000行以内,否则SQL很慢。

六、优化分页查询

分页语句:

查询排序的代价非常大

select * from student limit 2000000,10;

优化思路一:

适用于主键自增的表,把limit查询通过where过滤条件,转换成某个位置的查询

select * from student where id > 2000000 limit 10;

优化思路二:

在索引上完成排序分页操作查询出主键,最后根据主键关联回原表查询所需内容。

select * from student t,(select id from student order by id limit 2000000,10) a

where t.id=a.id;

七、覆盖索引的使用

1.什么是覆盖索引

①覆盖索引是一种数据查询方式,不是索引的类型。索引列+主键包括在select到from中

②通过索引值可以直接找到查询字段的值,不需要回表操作就是覆盖索引

如: CREATE INDEX idx_name_age ON user(name,age);

查询张三的年龄:

select name, age from user where name = 'zhangsan';

查询的字段name和age都在联合索引idx_name_age的索引树中,这样查询就是覆盖索引查询。

2.覆盖索引的利弊

好处:

①避免Innodb表进行二次查询,不需要回表

②可以把随机IO(回表)变成顺序IO加快查询效率

坏处:

索引字段的维护需要代价。

九、索引条件下推ICP

1.什么是索引条件下推ICP

在非聚簇索引中,判断部分能否使用索引中的列来检查,进行一下过滤,再进行回表操作。这样能减少回表查询次数,提高查询效率

2.案例分析

创建索引

CREATE INDEX idx_name_age ON tuser(name,age);

进行查询:只使用name索引,age失效

SELECT * FROM tuser

WHERE NAME LIKE '张%'

AND age = 10

AND ismale = 1;

没有索引条件下推

storage层:只将满足index key条件的索引记录对应的整行记录取出,返回给server层

server 层:对返回的数据,使用后面的where条件过滤,直至返回最后一行。

使用索引条件下推

storage层:

首先将index key条件满足的索引记录区间确定,然后在索引上使用index filter进行过滤。将满足的index filter条件的索引记录才去回表取出整行记录返回server层。不满足index filter条件的索引记录丢弃,不回表、也不会返回server层。

server 层:

对返回的数据,使用table filter条件做最后的过滤。

3.ICP使用条件

①只能用于二级索引

②表访问的类型是range(范围),ref(条件), ref_or_null(条件和空)

③ICP可以用于MyISAM和InnnoDB存储引擎

④InnnoDB存储引擎,ICP用于二级索引。ICP目标减少全行的读取次数,减少I/O操作。

九、其他查询优化策略

1.exists和in的区分

原则:小表驱动大表

①select * from A where id in (select id from B);

底层执行循环时先B后A,所以B小,A大用in

②select * from A where exists (select id from B where B.id=A.id);

底层执行循环时先A后B,所以A小,B大用exists

2.count(*)和count(具体字段)和count(1)的效率

①count(*)和count(1)本质上没区别

②在MyISAM存储引擎存储了表的行数,查询表的行数是O(1)复杂度。

如果是InnoDB存储引擎,需要全表扫描,复杂度O(n)。

③在InnoDB引擎下,count(具体字段)统计行数尽量不使用主键(信息多),使用非聚簇索引(信息小)。count(*)和count(1)会自动选择效率高的二级索引。

3.为什么不要用select(*),推荐select<字段列表>

①select(*)在MySQL解析中,会通过查询数据字典将*转换为所有列名,消耗时间,降低效率

②select(*)无法使用覆盖索引

4.limit 1对优化的影响

①如果是全表扫描且确定结果集是1,那么加上limit 1时找到一条结果就不继续扫描了,加快查询速度。

②如果对字段建立唯一索引,不会全表扫描,不需要加limit 1

5.多使用commit,为什么会提高性能

commit释放资源:

①回滚段上用于恢复数据的信息

②被程序语句获得锁

③redo / undo log buffer 中的空间

④管理上述 3 种资源中的内部花费

十、淘宝数据库的主键设计

1.自动ID出现的问题

①不可靠

存在自增id回溯的问题,MySQL8.0才修复

②不安全

对外暴露的接口可以才到对应的信息。/user/1接口,猜到用户ID的值多少,用户数量多少

③性能差

自增id由数据库服务端生成,性能差

④交互多

查询刚才插入的id,需要last_insert_id()函数。多1条SQL多一次性能开销。

⑤局部唯一性

当前数据库唯一。不是全局唯一。

2.业务字段做主键出现的问题

①选择卡号

虽然卡号是唯一的,但是卡号会重复使用。比如100001的张三退卡了,商家会把这个卡号给新用户。但是在业务层面会出现问题,如果交易表有会员卡号的字段,有王五购买的信息。此时新用户的交易记录会出现王五的交易流水信息。不能把卡号当主键

②电话或身份证

都属于用户的隐私。

原则:不用业务有关的字段作为主键。否则更改主键的成本非常高

3.淘宝主键的设计

订单ID = 时间 + 去重字段 + 用户ID后6位尾号

这样设计能做到全局唯一,对分布式友好

4.推荐的主键设计UUID

非核心业务:主键自增id,告警,日志,监控等信息

核心业务:全局唯一且单调自增

①UUID的特点

全局唯一,但是无序的(因为时间地位在前面,时间高位在后面),插入性能差

②对UUID进行改造

通过MySQL8.0提供的函数uuid_to_bin(UUID(),true);将UUID转换为有序的。全局唯一+单调递增。

猜你喜欢

转载自blog.csdn.net/jbkjhji/article/details/131595810