一、索引优化与查询优化
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转换为有序的。全局唯一+单调递增。