MySQL索引优化
什么是索引
索引(index)是帮助MySQL高效获取数据的数据结构
索引的目的在于提高查询效率,可以类比字典
可以理解为“排好序的快速查找数据结构”
数据本身之外,数据库还维护着一个满足特定算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引
一般来说索引本身也很大,不可能全部存储在内存中,因此索往往以索引文件的形式存储在磁盘上
我们平常所说的索引,如果没有特别指明,都是指B树(多路搜索,并不一定是二叉树)结构缓和的索引。其中聚集索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引。当然,除了B+树这种类型的索引外,还有哈稀索引。
优势:类似大学图片馆建书目索引,提高数据检索的效率,降低数据库的IO成本
通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗
劣势:
索引分类
- 单值索引:即一个索引只包含一个列,一个表可以有多个单列索引
- 唯一索引:索引列的值必须唯一,但允许为空值
- 复合索引:即一个索引包含多列
一张表索引最多不要超过5个
哪些情况需要创建索引
- 主键自动建立唯一索引
- 频繁作为查询条件的字段应该创建索引
- 查询中与其他表关联的字段,外键关系建立索引
- 频繁更新的字段不适合创建索引,因为每次更新不单单是更新了记录还会更新索引
- Where条件里用不到的字段不创建索引
- 单键/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
- 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
- 查询中统计或者分组字段
哪些情况不要创建索引
- 表记录太少
- 经常增删改的表
- 数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据建立索引
- 注意,如果某个数据列包含许多重复的内容(比如性别),为它建立索引就没有太大的实际效果
MySQL常见瓶颈
- CPU: CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时
- IO:磁盘I/0瓶颈发生装入数据远大于内存容量的时候
- 服务器硬件的性能瓶颈:top,free,iostat和vmstat来查看系统的性能状态
性能分析(Explain(查询执行计划))
explain能干嘛
- 表的读取顺序
- 数据读取操作的操作类型
- 哪些索引可以使用
- 哪些索引被实际使用
- 表之间的引用
- 每张表有多少行被优化器查询
执行计划包含的信息
- id
- id 相同,执行顺序由上至下
- id 不同,如果是子查询,数字大者先查询
- id相同不同
- select_type:
- SIMPLE:简单的select查询,查询中不包含子查询或union
- PRIMARY:查询中若包含任何复杂的子部分,最外层则被标记为PRIMARY
- SUBQUERY:在select 或where列表中包含了子查询
- DERIVED: 在from列表中包含的子查询被标记为DERIVED(衍生)MySQL会递归执行这些子查询,把结果放在临时表里
- UNION 若第二个SELECT出现在UNION之后,则被标记为UNION,若UNION包含在from子句的子查询中,外层select 将被标记为DERIVED
- UNION RESULT 从union表获取结果的select
- table
- partitions
- type: 访问类型
- 从最好到最差依次是system>const>eq_ref>ref>range>index>ALL
- 一般来说,得保证查询至少达到range组别,最好达到ref
- system 表只有一行记录(等于系统表),这是const类型特例,平时不会出现,这个可以忽略不计
- const 表示通过索引一次就找到了,const用于比较primary key 或unique索引,因为只匹配一行数据,所以很快。如将主键置于where列表中,Mysql就能将该查询转换为一个常量。
- eq_ref 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。
- ref 非唯一性扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。
- range 只检索给定范围的行,使用一个索引来选择行。key列显示来使用哪个索引,一般就是在你的where语句中出现了between、<、>、in等的查询,这种范围扫描索引比全表扫描要好,因为他只开始于索引的某一点,而结束于另一点,不用扫描全部索引。
- index: Full Index Scan, index与all区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小(也就是说,虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘中读的)
- possible_keys:显示可能应用在这张表中的索引,可能是1个或多个;查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
- key:实际使用的索引。如果为NULL,则没有使用索引,查询中若使用了覆盖索引,则该索引仅出现在key列表中。
- key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精度的情况下,长度越短越好。key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。
- ref:显示索引的哪一列被使用了,如果可能的话,是一个常数,哪些列或常量被用于查找索引列上的值。
- rows:根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。
- filtered
- Extra:包含不适合在其他列中显示但十分重要的额外信息
- Using filesort:说明mysql会对数据使用的一个外部的索引排序,而不是按照表内的索引顺序进行读取。Mysql中无法利用索引完成的排序操作称为"文件排序"。
- Using temporary: 使用了临时表保存了中间结果,Mysql在对查询结果排序时使用临时表。常见于排序order by 和分组group by。例EXPLAIN SELECT * from user GROUP BY name (此时的索引建为 index_age_name)
- Using index: 表示相应的selectr操作中使用了覆盖索引(Covering index),避免了广访问了表的数据行,效率不错。如果同时出现using where,表示索引被用来执行索引键值的查找。如果没有同时出现using where,表明索引用来读取数据而非执行查找动作。(覆盖索引:select 的数据列只用从索引中就能够取得,不必读数据行,Mysql可以利用索引返回select列表中的字段,而不必根据索引两再次读取数据文件,换句话说查询列要被所建的索引覆盖。)
- using where :表明使用了where过滤
- using join buffer: 表明使用了连接缓存
- impossible where:where 子句的值总是false,不能用来获取任何元素(EXPLAIN SELECT * from user where age = 7 and age = 8)
- Select tables optimized away :在没有group by 子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化count(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。(EXPLAIN SELECT min(age) from user)
- distinct:优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作。
索引优化
- 两张表左连接,应在右表的连接字段上建立索引
- 三张表左连接,应在两张右表的连接字段上建立索引
- 右连接则相反
索引失效
- 全值匹配我最爱
- 最佳左前缀法则(带头大哥不能死,中间兄弟不能断)
- 不在索引列上做任何操作(计算,函数,自动或手动类型转换),会导致索引失效而转向全表扫描
- 存储引擎不能使用索引中范围条件右边的列
- 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *
- mysql 在使用不等于(!= 或<>)的时候无法使用索引会导致全表扫描
- is null 和 is not null 也无法使用索引
- like 以通配符开头(’%abc’)mysql索引失效会变成全表扫描的操作(覆盖索引(见5)可以解决like两端百分号的问题)
- 字符串不加单引号索引失效
- 少用or,用它来连接时会索引失效
- 小总结:全值匹配我最爱,最左前缀要遵守;带头大哥不能死,中间兄弟不能断;索引列上少计算,范围之后全失效;LIKE百分写最右,覆盖索引不写星;不等空值还有OR,索引失效要少用;VAR引号不可丢,SQL高级也不难。
查询截取分析
分析
- 观察,至少跑一天,看看生产的慢SQL情况。
- 开启慢查询日志,设置阈值,比如超过5秒钟的就是慢SQL,并将它抓取出来。
- explain+慢SQL分析
- show profile
- 运维经理 or DBA ,进行SQL数据库服务器的参数调优。
in 和exist(小表驱动大表)
排序
group by 关键字优化
- group by 实质是先排序后分组,遵照索引建的最佳左前缀
- 当无法使用索引列,增大max_length_for_sort_data参数的设置和增大sort_buffer_size参数的设置。
- where 高于 having,能写在where限定的条件就不要去having限定了。
慢查询日志
- 查看是否开启:SHOW VARIABLES LIKE ‘%slow_query_log%’
- 开启:set global slow_query_log=1(只对当前数据库生效,如果MySQL重启后则会失效)
- 那么开启了慢查询日志后,什么样的SQL才会记录到慢查询日志里面呢?
- 假如运行时间 正好等于long_query_time的情况,并不会被记录下来。也就是说,在MySQL源码里是判断大于long_query_time,而非大于等于。
- 休眠4秒,select sleep(4)
- 查询慢查询语句的数量: show global status like ‘%slow_queries%’
mysql 慢查询日志分析工具:mysqldumpslow
锁
- lock table 表名字 read(write), 表名字2 read(write),其他;
- session1 中对表加了读锁,对该表可以有读操作,不能有写操作,也不能对其他表有读和写操作,session2中可以对该表有读操作,若对他有写操作,会被阻塞,直到session1中对该表释放锁,session2中的被阻塞的写操作才会被执行。
4. 简而言之,就是读锁会阻塞写,但是不会阻塞读。而写锁则会把读和写都阻塞。
5. 查看哪些表被加了锁:show OPEN TABLES
6. 可以通过检查Table_locks_immediate 和Table_locks_waited状态变量来分析系统上的表锁定;SQL: show STATUS like ‘table%’
7. Table_locks_immediate: 产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁值加1;
8. Table_locks_waited:出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每等待一次锁值加1),此值高则说明存在着较严重的表级锁争用情况。
9. 此外,Myisam的读写锁调度是写优先,这也是Myisam不适合做写为主表的引擎。因为写锁后,其他 线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞。
事务
行锁变表锁
- (导致原因,update table set a = 4000 where id = 1), 因为a是varchar,没有加单引号,导致索引失效。
间隙锁危害
select * from a where a= 8 for update 锁定某一行,其他操作会阻塞,直到commit后,其他操作才会执行。
show status like ‘%innodb_row_lock%’
Innodb_row_lock_current_waits:当前正在等待锁定的数量
Innodb_row_lock_waits 系统启动后到现在总共等待的次数
优化建议
- 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁。
- 合理设计索引,尽量缩小锁的范围。
- 尽可能较少检索条件,避免间隙锁。
- 尽量控制事务大小,减少锁定资源量和时间长度。
- 尽可能低级别事务隔离。
主从复制
- mysql 版本一致且后台以服务运行。