SQL优化 · 经典案例 · 索引篇
Introduction
在这些年的工作之中,由于SQL问题导致的数据库故障层出不穷,下面将过去六年工作中遇到的SQL问题总结归类,还原问题原貌,给出分析问题思路和解决问题的方法,帮助用户在使用数据库的过程中能够少走一些弯路。总共包括四部分:索引篇,SQL改写篇,参数优化篇,优化器篇四部分,今天将介绍第一部分:索引篇。
索引问题是SQL问题中出现频率最高的,常见的索引问题包括:无索引,隐式转换。当数据库中出现访问表的SQL无索引导致全表扫描,如果表的数据量很大,扫描大量的数据,应用请求变慢占用数据库连接,连接堆积很快达到数据库的最大连接数设置,新的应用请求将会被拒绝导致故障发生。隐式转换是指SQL查询条件中的传入值与对应字段的数据定义不一致导致索引无法使用。常见隐士转换如字段的表结构定义为字符类型,但SQL传入值为数字;或者是字段定义collation为区分大小写,在多表关联的场景下,其表的关联字段大小写敏感定义各不相同。隐式转换会导致索引无法使用,进而出现上述慢SQL堆积数据库连接数跑满的情况。
无索引案例:
表结构
CREATE TABLE `user` (
……
mo bigint NOT NULL DEFAULT '' ,
KEY ind_mo (mo)
……
) ENGINE=InnoDB;
SELECT uid FROM `user` WHERE mo=13772556391 LIMIT 0,1
执行计划
mysql> explain  SELECT uid FROM `user` WHERE mo=13772556391 LIMIT 0,1;
           id: 1
  select_type: SIMPLE
        table: user
         type: ALL
possible_keys: NULL
          key: NULL
         rows: 707250
         Extra: Using where
从上面的SQL看到执行计划中ALL,代表了这条SQL执行计划是全表扫描,每次执行需要扫描707250行数据,这是非常消耗性能的,该如何进行优化?添加索引。 
验证mo字段的过滤性
mysql> select count(*) from user where mo=13772556391;
|   0    |
可以看到mo字段的过滤性是非常高的,进一步验证可以通过select count(*) as all_count,count(distinct mo) as distinct_cnt from user,通对比 all_count和distinct_cnt这两个值进行对比,如果all_cnt和distinct_cnt相差甚多,则在mo字段上添加索引是非常有效的。
添加索引
mysql> alter table user add index ind_mo(mo);
mysql>SELECT uid FROM `user` WHERE mo=13772556391 LIMIT 0,1;
Empty set (0.05 sec)
执行计划
mysql> explain  SELECT uid FROM `user` WHERE mo=13772556391 LIMIT 0,1\G;
*************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: user
             type: index
    possible_keys: ind_mo
              key: ind_mo
             rows: 1
            Extra: Using where; Using index
隐式转换案例一
表结构
  CREATE TABLE `user` (
  ……
  mo char(11) NOT NULL DEFAULT '' ,
  KEY ind_mo (mo)
  ……
  ) ENGINE=InnoDB;
执行计划
mysql> explain extended select uid from`user` where mo=13772556391 limit 0,1;
mysql> show warnings;
Warning1:Cannot use  index 'ind_mo' due to type or collation conversion on field 'mo'
Note:select `user`.`uid` AS `uid` from `user` where (`user`.`mo` = 13772556391) limit 0,1
如何解决
mysql> explain   SELECT uid FROM `user` WHERE mo='13772556391' LIMIT 0,1\G;
*************************** 1. row ***************************
              id: 1
     select_type: SIMPLE
           table: user
            type: ref
   possible_keys: ind_mo
             key: ind_mo
            rows: 1
           Extra: Using where; Using index
上述案例中由于表结构定义mo字段后字符串数据类型,而应用传入的则是数字,进而导致了隐式转换,索引无法使用,所以有两种方案: 
第一,将表结构mo修改为数字数据类型。 
第二,修改应用将应用中传入的字符类型改为数据类型。
隐式转换案例二
表结构
CREATE TABLE `test_date` (
     `id` int(11) DEFAULT NULL,
     `gmt_create` varchar(100) DEFAULT NULL,
     KEY `ind_gmt_create` (`gmt_create`)
) ENGINE=InnoDB AUTO_INCREMENT=524272;
5.5版本执行计划
mysql> explain  select * from test_date where gmt_create BETWEEN DATE_ADD(NOW(), INTERVAL - 1 MINUTE) AND   DATE_ADD(NOW(), INTERVAL 15 MINUTE) ;
+----+-------------+-----------+-------+----------------+----------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys  | key | key_len | ref  | rows | Extra       |
+----+-------------+-----------+-------+----------------+----------------+---------+------+------+-------------+
|1|SIMPLE| test_date |range| ind_gmt_create|ind_gmt_create|303| NULL | 1 | Using where |
5.6版本执行计划
mysql> explain select * from test_date where gmt_create BETWEEN DATE_ADD(NOW(), INTERVAL - 1 MINUTE) AND   DATE_ADD(NOW(), INTERVAL 15 MINUTE) ;
+----+-------------+-----------+------+----------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys  | key  | key_len | ref | rows | Extra|
+----+-------------+-----------+------+----------------+------+---------+------+---------+-------------+
| 1 | SIMPLE| test_date | ALL | ind_gmt_create | NULL | NULL | NULL | 2849555 | Using where |
+----+-------------+-----------+------+----------------+------+---------+------+---------+-------------+
|Warning|Cannot use range access on index 'ind_gmt_create' due to type on field 'gmt_create' 
上述案例是用户在5.5版本升级到5.6版本后出现的隐式转换,导致数据库cpu压力100%,所以我们在定义时间字段的时候一定要采用时间类型的数据类型。
隐式转换案例三
表结构
  CREATE TABLE `t1` (
  `c1` varchar(100) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
  `c2` varchar(100) DEFAULT NULL,
  KEY `ind_c1` (`c1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 
CREATE TABLE `t2` (
  `c1` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `c2` varchar(100) DEFAULT NULL,
  KEY `ind_c2` (`c2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
执行计划
mysql> explain     select t1.* from  t2 left  join  t1 on t1.c1=t2.c1 where t2.c2='b';
+----+-------------+-------+------+---------------+--------+---------+-------+--------+-------------+
| id | select_type | table | type | possible_keys |key| key_len | ref   | rows   | Extra    |
+----+-------------+-------+------+---------------+--------+---------+-------+--------+-------------+
| 1 | SIMPLE | t2 | ref  | ind_c2 | ind_c2 | 303     | const |    258 | Using where |
|1  |SIMPLE |t1  |ALL   | NULL   | NULL   | NULL    | NULL  | 402250 |    |
修改COLLATE
mysql> alter table t1 modify column c1 varchar(100) COLLATE utf8_bin ;
Query OK, 401920 rows affected (2.79 sec)
Records: 401920  Duplicates: 0  Warnings: 0
执行计划
mysql> explain   select t1.* from  t2 left  join  t1 on t1.c1=t2.c1 where t2.c2='b';
+----+-------------+-------+------+---------------+--------+---------+------------+-------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref | rows  | Extra       |
+----+-------------+-------+------+---------------+--------+---------+------------+-------+-------------+
|  1 | SIMPLE| t2| ref | ind_c2| ind_c2 | 303     | const      |   258 | Using where |
|  1 |SIMPLE| t1|ref| ind_c1  | ind_c1 | 303     | test.t2.c1 | 33527 |             |
+----+-------------+-------+------+---------------+--------+---------+------------+-------+-------------+
可以看到修改了字段的COLLATE后执行计划使用到了索引,所以一定要注意表字段的collate属性的定义保持一致。
两个索引的常见误区
- 误区一:对查询条件的每个字段建立单列索引,例如查询条件为:A=?and B=?and C=?。 
 在表上创建了3个单列查询条件的索引ind_A(A),ind_B(B),ind_C(C),应该根据条件的过滤性,创建适当的单列索引或者组合索引。
- 误区二:对查询的所有字段建立组合索引,例如查询条件为select A,B,C,D,E,F from T where G=?。 
 在表上创建了ind_A_B_C_D_E_F_G(A,B,C,D,E,F,G)。
索引最佳实践
- 在使用索引时,我们可以通过explain+extended查看SQL的执行计划,判断是否使用了索引以及发生了隐式转换。
- 由于常见的隐式转换是由字段数据类型以及collation定义不当导致,因此我们在设计开发阶段,要避免数据库字段定义,避免出现隐式转换。
- 由于MySQL不支持函数索引,在开发时要避免在查询条件加入函数,例如date(gmt_create)。
- 所有上线的SQL都要经过严格的审核,创建合适的索引。
SQL优化 · 经典案例 · 索引篇的更多相关文章
- sql优化经典例子
		场景 我用的数据库是mysql5.6,下面简单的介绍下场景 课程表 create table Course( c_id int PRIMARY KEY, name varchar(10) ) 数据10 ... 
- SQL优化 MySQL版  - 索引分类、创建方式、删除索引、查看索引、SQL性能问题
		SQL优化 MySQL版 - 索引分类.创建方式.删除索引.查看索引.SQL性能问题 作者 Stanley 罗昊 [转载请注明出处和署名,谢谢!] 索引分类 单值索引 单的意思就是单列的值,比如说有 ... 
- SQL优化基础 使用索引(一个小例子)
		按照本文操作和体会,会对sql优化有个基本最简单的了解,其他深入还需要更多资料和实践的学习: 1. 建表: 复制代码代码如下: create table site_user ( id int IDEN ... 
- sql优化策略之索引失效情况二
		详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp63 接第一篇索引失效分析:http://grefr.iteye.co ... 
- MySQL SQL优化之字符串索引隐式转换
		之前有用户很不解:SQL语句非常简单,就是select * from test_1 where user_id=1 这种类型,而且user_id上已经建立索引了,怎么还是查询很慢? test_1的表结 ... 
- SQL Server 经典案例
		1.先进先出 例1 WITH [ta] ([商品编号], [批次号], [库存数量]) AS ( UNION ALL UNION ALL UNION ALL ),[tb] ([商品编号], [订货数量 ... 
- Oracle学习总结(5)—— SQL语句经典案例
		--0.所有员工信息 SELECT * FROM emp --1.选择部门30的所有员工 SELECT * FROM emp WHERE deptno=20 --2.列出所有办事员(CLERK)的姓名 ... 
- 浅谈SQL优化入门:3、利用索引
		0.写在前面的话 关于索引的内容本来是想写的,大概收集了下资料,发现并没有想象中的简单,又不想总结了,纠结了一下,决定就大概写点浅显的,好吧,就是懒,先挖个浅坑,以后再挖深一点.最基本的使用很简单,直 ... 
- SQL优化笔记一:索引和explain
		目录 为什么需要优化SQL SQL优化的重点 索引 索引的结构 索引的优缺点总结: 索引的分类 索引操作 B树 实战 问题 数据库方面,我会使用MySQL来讲解 为什么需要优化SQL 性能低,执行时间 ... 
随机推荐
- Java学习笔记(一)语法
			基本语法 大小写敏感 类名:对于所有的类来说,类名的首字母应该大写 方法名:所有的方法名都应该以小写字母开头.如果方法名含有若干单词,则后面的每个单词首字母大写. 源文件名:源文件名必须和类名相同.当 ... 
- ASP.NET CORE系列【四】基于Claim登录授权
			介绍 关于什么是Claim? 可以看看其他大神的文章: http://www.cnblogs.com/jesse2013/p/aspnet-identity-claims-based-authenti ... 
- Spring第一天:Spring的概述、SpringIOC入门(XML)、Spring的Bean管理、Spring属性注入
			记得引入约束 上图路径. 此时 只需修改配置文件 便可以随意更换实现类 无需修改代码. 传统方法必须用实现类(不面向接口了)来调用方法设置属性. 而在Spring中:在创建类的过程中发现实现类有nam ... 
- CAS客户端整合(三) Otrs
			OTRS 是用Perl写的一个工单邮件系统,非常强大. 登录流程 流程图略过 otrs没有像 discuz 和 zabbix 类似的游客登录状态,这样处理起来逻辑分支少一些. 不过还是考虑用 otrs ... 
- 解读人:李琼,Metabolic profiling of tumors, sera and skeletal muscles from an orthotopic murine model of gastric cancer associated-cachexia(胃癌相关恶病质的原位小鼠模型中肿瘤,血清和骨骼肌的代谢谱分析)
			发表时间:(2019年4月) IF:3.950 单位: 厦门大学 厦门理工大学 物种:小鼠的肿瘤,血清和骨骼肌 技术:核磁共振代谢组学分析 一. 概述:(用精炼的语言描述文章的整体思路及结果) 本研究 ... 
- 对zabbix监控磁盘性能的补充
			原因 在上一篇文章中,我写了完整的磁盘监控步骤,希望对大家有所帮助.但是这里还需要作出一点补充. 根据上一篇文章的内容,我是使用iostat命令不停的收集磁盘的信息,然后写入到/tmp/iostat_ ... 
- thinphp5会员注册邮箱验证
			1.首先完成邮箱发送http://www.cnblogs.com/jcydd/p/7299750.html 2.在完成会员新增后执行后置函数,在模型类当中 //注册后置函数 protected sta ... 
- js中去掉字符中间空格和首尾空格
			转载: https://www.jb51.net/article/109522.htm 1. 去掉字符串前后所有空格: 代码如下: ? 1 2 3 4 function Trim(str) { ... 
- Carryon的字符串
			I J I: Carryon的字符串 时间限制: 1 s 内存限制: 128 MB 提交 我的状态 题目描述 Carryon最近喜欢上了一些奇奇怪怪的字符,字符都是英文小写字母,但 ... 
- Sorted Subsegments
			https://www.hackerrank.com/contests/101hack38/challenges/sorted-subsegments/problem 首先要注意到可以二分答案,比如当 ... 
