本文主要是总结了工做中一些经常使用的操做,以及不合理的操做,在对慢查询进行优化时收集的一些有用的资料和信息,本文适合有mysql基础的开发人员。html
1、索引相关
- 索引基数:基数是数据列所包含的不一样值的数量。例如,某个数据列包含值一、三、七、四、七、3,那么它的基数就是4。索引的基数相对于数据表行数较高(也就是说,列中包含不少不一样的值,重复的值不多)的时候,它的工做效果最好。若是某数据列含有不少不一样的年龄,索引会很快地分辨数据行。若是某个数据列用于记录性别(只有"M"和"F"两种值),那么索引的用处就不大。若是值出现的概率几乎相等,那么不管搜索哪一个值均可能获得一半的数据行。在这些状况下,最好根本不要使用索引,由于查询优化器发现某个值出如今表的数据行中的百分比很高的时候,它通常会忽略索引,进行全表扫描。惯用的百分比界线是"30%"。
-
索引失效缘由:mysql
- 对索引列运算,运算包括(+、-、*、/、!、<>、%、like'%_'(%放在前面)
- 类型错误,如字段类型为varchar,where条件用number。
- 对索引应用内部函数,这种状况下应该创建基于函数的索引
如select * from template t where ROUND(t.logicdb_id) = 1
此时应该建ROUND(t.logicdb_id)为索引,mysql8.0开始支持函数索引,5.7能够经过虚拟列的方式来支持,以前只能新建一个ROUND(t.logicdb_id)列而后去维护 - 若是条件有or,即便其中有条件带索引也不会使用(这也是为何建议少使用or的缘由),若是想使用or,又想索引有效,只能将or条件中的每一个列加上索引
- 若是列类型是字符串,那必定要在条件中数据使用引号,不然不使用索引;
- B-tree索引 is null不会走,is not null会走,位图索引 is null,is not null 都会走
- 组合索引遵循最左原则
索引的创建redis
- 最重要的确定是根据业务常常查询的语句
- 尽可能选择区分度高的列做为索引,区分度的公式是 COUNT(DISTINCT col) / COUNT(*)。表示字段不重复的比率,比率越大咱们扫描的记录数就越少
- 若是业务中惟一特性最好创建惟一键,一方面能够保证数据的正确性,另外一方面索引的效率能大大提升
2、EXPLIAN中有用的信息
基本用法算法
- desc 或者 explain 加上你的sql
- extended explain加上你的sql,而后经过show warnings能够查看实际执行的语句,这一点也是很是有用的,不少时候不一样的写法通过sql分析以后实际执行的代码是同样的
提升性能的特性spring
- 索引覆盖(covering index):须要查询的数据在索引上均可以查到不须要回表 EXTRA列显示using index
- ICP特性(Index Condition Pushdown):原本index仅仅是data access的一种访问模式,存数引擎经过索引回表获取的数据会传递到MySQL server层进行where条件过滤,5.6版本开始当ICP打开时,若是部分where条件能使用索引的字段,MySQL server会把这部分下推到引擎层,能够利用index过滤的where条件在存储引擎层进行数据过滤。EXTRA显示using index condition。须要了解mysql的架构图分为server和存储引擎层
- 索引合并(index merge):对多个索引分别进行条件扫描,而后将它们各自的结果进行合并(intersect/union)。通常用OR会用到,若是是AND条件,考虑创建复合索引。EXPLAIN显示的索引类型会显示index_merge,EXTRA会显示具体的合并算法和用到的索引
extra字段
一、using filesort: 说明MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中没法利用索引完成的排序操做称为“文件排序” ,其实不必定是文件排序,内部使用的是快排
二、using temporary: 使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序order by和分组查询group by
三、using index: 表示相应的SELECT操做中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错。
六、impossible where: WHERE子句的值老是false,不能用来获取任何元组
七、select tables optimized away: 在没有GROUP BY子句的状况下基于索引优化MIN/MAX操做或者对于MyISAM存储引擎优化COUNT(*)操做, 没必要等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化
八、distinct: 优化distinct操做,在找到第一匹配的元祖后即中止找一样值的操做sql
using filesort,using temporary这两项出现时须要注意下,这两项是十分耗费性能的,在使用group by的时候,虽然没有使用order by,若是没有索引,是可能同时出现using filesort,using temporary的,由于group by就是先排序在分组,若是没有排序的须要,能够加上一个order by NULL来避免排序,这样using filesort就会去除,能提高一点性能。docker
type字段
system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现
const:若是经过索引依次就找到了,const用于比较主键索引或者unique索引。 由于只能匹配一行数据,因此很快。若是将主键置于where列表中,MySQL就能将该查询转换为一个常量
eq_ref:惟一性索引扫描,对于每一个索引键,表中只有一条记录与之匹配。常见于主键或惟一索引扫描
ref:非惟一性索引扫描,返回匹配某个单独值的全部行。本质上也是一种索引访问,它返回全部匹配 某个单独值的行,然而它可能会找到多个符合条件的行,因此它应该属于查找和扫描的混合体
range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪一个索引,通常就是在你的where语句中出现between、<、>、in等的查询,这种范围扫描索引比全表扫描要好,由于只须要开始于缩印的某一点,而结束于另外一点,不用扫描所有索引
index:Full Index Scan ,index与ALL的区别为index类型只遍历索引树,这一般比ALL快,由于索引文件一般比数据文件小。 (也就是说虽然ALL和index都是读全表, 但index是从索引中读取的,而ALL是从硬盘读取的)
all:Full Table Scan,遍历全表得到匹配的行数据库
参考地址:http://www.javashuo.com/article/p-duvfxotv-hr.html缓存
3、字段类型和编码
- mysql返回字符串长度:CHARACTER_LENGTH方法(CHAR_LENGTH同样的)返回的是字符数,LENGTH函数返回的是字节数,一个汉字三个字节
- varvhar等字段创建索引长度计算语句:select count(distinct left(test,5))/count(*) from table; 越趋近1越好
- mysql的utf8最大是3个字节不支持emoji表情符号,必须只用utf8mb4。须要在mysql配置文件中配置客户端字符集为utf8mb4。jdbc的链接串不支持配置characterEncoding=utf8mb4,最好的办法是在链接池中指定初始化sql,例如:hikari链接池,其余链接池相似spring.datasource.hikari.connection-init-sql=set names utf8mb4。不然须要每次执行sql前都先执行set names utf8mb4。
- msyql排序规则(通常使用_bin和_genera_ci):
- utf8_genera_ci不区分大小写,ci为case insensitive的缩写,即大小写不敏感,
- utf8_general_cs区分大小写,cs为case sensitive的缩写,即大小写敏感,可是目前MySQL版本中已经不支持相似于***_genera_cs的排序规则,直接使用utf8_bin替代。
-
utf8_bin将字符串中的每个字符用二进制数据存储,区分大小写。markdown
那么,一样是区分大小写,utf8_general_cs和utf8_bin有什么区别?
cs为case sensitive的缩写,即大小写敏感;bin的意思是二进制,也就是二进制编码比较。
utf8_general_cs排序规则下,即使是区分了大小写,可是某些西欧的字符和拉丁字符是不区分的,好比ä=a,可是有时并不须要ä=a,因此才有utf8_bin
utf8_bin的特色在于使用字符的二进制的编码进行运算,任何不一样的二进制编码都是不一样的,所以在utf8_bin排序规则下:ä<>a
- sql yog中初始链接指定编码类型使用链接配置的初始化命令
4、SQL语句总结
经常使用的但容易忘的:
- 若是有主键或者惟一键冲突则不插入:insert ignore into
- 若是有主键或者惟一键冲突则更新,注意这个会影响自增的增量:INSERT INTO
room_remarks
(room_id,room_remarks) VALUE(1,"sdf") ON DUPLICATE KEY UPDATE room_remarks="234" - 若是有就用新的替代,values若是不包含自增列,自增列的值会变化: REPLACE INTO
room_remarks
(room_id,room_remarks) VALUE(1,"sdf") - 备份表:CREATE TABLE user_info SELECT * FROM user_info
- 复制表结构:CREATE TABLE user_v2 LIKE user
- 从查询语句中导入:INSERT INTO user_v2 SELECT * FROM user或者INSERT INTO user_v2(id,num) SELECT id,num FROM user
- 连表更新:UPDATE user a, room b SET a.num=a.num+1 WHERE a.room_id=b.id
- 连表删除:DELETE user FROM user,black WHERE user.id=black.id
锁相关(做为了解,不多用)
- 共享锁: select id from tb_test where id = 1 lock in share mode;
- 排它锁: select id from tb_test where id = 1 for update
优化时用到:
- 强制使用某个索引: select * from table force index(idx_user) limit 2;
- 禁止使用某个索引: select * from table ignore index(idx_user) limit 2;
- 禁用缓存(在测试时去除缓存的影响): select SQL_NO_CACHE from table limit 2;
查看状态
- 查看字符集 SHOW VARIABLES LIKE 'character_set%';
- 查看排序规则 SHOW VARIABLES LIKE 'collation%';
SQL编写注意
- where语句的解析顺序是从右到左,条件尽可能放where不要放having
- 采用延迟关联(deferred join)技术优化超多分页场景,好比limit 10000,10,延迟关联能够避免回表
- distinct语句很是损耗性能,能够经过group by来优化
- 连表尽可能不要超过三个表
5、踩坑
- 若是有自增列,truncate语句会把自增列的基数重置为0,有些场景用自增列做为业务上的id须要十分重视
- 聚合函数会自动滤空,好比a列的类型是int且所有是NULL,则SUM(a)返回的是NULL而不是0
- mysql判断null相等不能用“a=null”,这个结果永远为UnKnown,where和having中,UnKnown永远被视为false,check约束中,UnKnown就会视为true来处理。因此要用“a is null”处理
6、千万大表在线修改
mysql在表数据量很大的时候,若是修改表结构会致使锁表,业务请求被阻塞。mysql在5.6以后引入了在线更新,可是在某些状况下仍是会锁表,因此通常都采用pt工具( Percona Toolkit)
如对表添加索引:
pt-online-schema-change --user='root' --host='localhost' --ask-pass --alter "add index idx_user_id(room_id,create_time)" D=fission_show_room_v2,t=room_favorite_info --execute
7、慢查询日志
有时候若是线上请求超时,应该去关注下慢查询日志,慢查询的分析很简单,先找到慢查询日志文件的位置,而后利用mysqldumpslow去分析。查询慢查询日志信息能够直接经过执行sql命令查看相关变量,经常使用的sql以下:
-- 查看慢查询配置 -- slow_query_log 慢查询日志是否开启 -- slow_query_log_file 的值是记录的慢查询日志到文件中 -- long_query_time 指定了慢查询的阈值 -- log_queries_not_using_indexes 是否记录全部没有利用索引的查询 SHOW VARIABLES LIKE '%quer%'; -- 查看慢查询是日志仍是表的形式 SHOW VARIABLES LIKE 'log_output' -- 查看慢查询的数量 SHOW GLOBAL STATUS LIKE 'slow_queries';
mysqldumpslow的工具十分简单,我主要用到的是参数以下:
-t:限制输出的行数,我通常取前十条就够了
-s:根据什么来排序默认是平均查询时间at,我还常常用到c查询次数,由于查询次数很频繁可是时间不高也是有必要优化的,还有t查询时间,查看那个语句特别卡。
-v:输出详细信息
例子:mysqldumpslow -v -s t -t 10 mysql_slow.log.2018-11-20-0500
8、查看sql进程和杀死进程
若是你执行了一个sql的操做,可是迟迟没有返回,你能够经过查询进程列表看看他的实际执行情况,若是该sql十分耗时,为了不影响线上能够用kill命令杀死进程,经过查看进程列表也能直观的看下当前sql的执行状态,若是当前数据库负载很高,在进程列表可能会出现,大量的进程夯住,执行时间很长。命令以下:
--查看进程列表 SHOW PROCESSLIST; --杀死某个进程 kill 183665
若是你使用的sqlyog,那么也有图形化的页面,在菜单栏-工具-显示-进程列表。在进程列表页面能够右键杀死进程。以下所示:
9、一些数据库性能的思考
在对公司慢查询日志作优化的时候,不少时候多是忘了建索引,像这种问题很容易解决,加个索引就好了。可是有两种状况就不是简单能加索引能解决了:
- 业务代码循环读数据库: 考虑这样一个场景,获取用户粉丝列表信息 加入分页是十个 其实像这样的sql是十分简单的,经过连表查询性能也很高,可是有时候,不少开发采用了取出一串id,而后循环读每一个id的信息,这样若是id不少对数据库的压力是很大的,并且性能也很低
- 统计sql:不少时候,业务上都会有排行榜这种,发现公司有不少地方直接采用数据库作计算,在对一些大表的作聚合运算的时候,常常超过五秒,这些sql通常很长并且很难优化, 像这种场景,若是业务容许(好比一致性要求不高或者是隔一段时间才统计的),能够专门在从库里面作统计。另外我建议仍是采用redis缓存来处理这种业务
- 超大分页: 在慢查询日志中发现了一些超大分页的慢查询如
limit 40000,1000
,由于mysql的分页是在server层作的,能够采用延迟关联在减小回表。可是看了相关的业务代码正常的业务逻辑是不会出现这样的请求的,因此颇有多是有恶意用户在刷接口,因此最好在开发的时候也对接口加上校验拦截这些恶意请求。