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优化 · 经典案例 · 索引篇的更多相关文章

  1. sql优化经典例子

    场景 我用的数据库是mysql5.6,下面简单的介绍下场景 课程表 create table Course( c_id int PRIMARY KEY, name varchar(10) ) 数据10 ...

  2. SQL优化 MySQL版 - 索引分类、创建方式、删除索引、查看索引、SQL性能问题

    SQL优化 MySQL版  - 索引分类.创建方式.删除索引.查看索引.SQL性能问题 作者 Stanley 罗昊 [转载请注明出处和署名,谢谢!] 索引分类 单值索引 单的意思就是单列的值,比如说有 ...

  3. SQL优化基础 使用索引(一个小例子)

    按照本文操作和体会,会对sql优化有个基本最简单的了解,其他深入还需要更多资料和实践的学习: 1. 建表: 复制代码代码如下: create table site_user ( id int IDEN ...

  4. sql优化策略之索引失效情况二

    详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp63   接第一篇索引失效分析:http://grefr.iteye.co ...

  5. MySQL SQL优化之字符串索引隐式转换

    之前有用户很不解:SQL语句非常简单,就是select * from test_1 where user_id=1 这种类型,而且user_id上已经建立索引了,怎么还是查询很慢? test_1的表结 ...

  6. SQL Server 经典案例

    1.先进先出 例1 WITH [ta] ([商品编号], [批次号], [库存数量]) AS ( UNION ALL UNION ALL UNION ALL ),[tb] ([商品编号], [订货数量 ...

  7. Oracle学习总结(5)—— SQL语句经典案例

    --0.所有员工信息 SELECT * FROM emp --1.选择部门30的所有员工 SELECT * FROM emp WHERE deptno=20 --2.列出所有办事员(CLERK)的姓名 ...

  8. 浅谈SQL优化入门:3、利用索引

    0.写在前面的话 关于索引的内容本来是想写的,大概收集了下资料,发现并没有想象中的简单,又不想总结了,纠结了一下,决定就大概写点浅显的,好吧,就是懒,先挖个浅坑,以后再挖深一点.最基本的使用很简单,直 ...

  9. SQL优化笔记一:索引和explain

    目录 为什么需要优化SQL SQL优化的重点 索引 索引的结构 索引的优缺点总结: 索引的分类 索引操作 B树 实战 问题 数据库方面,我会使用MySQL来讲解 为什么需要优化SQL 性能低,执行时间 ...

随机推荐

  1. 【Android-stdio-appdemo搭建记录】

    1-如何删除存在的工程 2-创建Android项目 next设置app兼容最低版本:api15--android 4.0以上 创建活动页面 设置活动页面的名称 创建成功项目以后会有最基本的layout ...

  2. opesntack基础知识-软件包历史脉络

    软件包管理 软件包管理是每个OpenStack项目的基础,其目的是用来将项目代码打包成源码包或者二进制包进行分发.一个项目的代码可能会被打包放到PyPI上,这样你可以通过pip命令安装这个包:也可能会 ...

  3. 复制或合并map

    1.map之间实现复制或合并 { // Map 能像数组一样被复制: let original = new Map([ [1, 'tom'] ]); let clone = new Map(origi ...

  4. Educational Codeforces Round 52E(构造,快速幂)

    #include <bits/stdc++.h>using namespace std;const int mod=998244353;long long b[200007];long l ...

  5. nginx 反向代理及负载均衡

    假设我们在 192.168.137.11:8080 上有一个web服务,在 192.168.137.12 配置了一台 nginx,我们可以进行如下配置: location / {    proxy_p ...

  6. [转][iOS Crash文件分析]-如何使用symbolicatecrash工具

    上传一软件,被拒了,自己测试了n遍都未出现这个情况,今天用symbolicatecrash工具分析了一下苹果提供的Crash文件,最终查到是 - (void)mapView:(MKMapView *) ...

  7. GPT分区

    GUID分区表(简称GPT.使用GUID分区表的磁盘称为GPT磁盘).它具有如下优点: 1.支持2TB以上的硬盘.    2.分区个数没有限制.但是Windows系统最多支持128个分区.    3. ...

  8. css奇技淫巧—box-shadow与outline绘制多重边框效果

    css语法: box-shadow: h-shadow v-shadow blur spread color inset; 注释:box-shadow 向框添加一个或多个阴影.该属性是由逗号分隔的阴影 ...

  9. 如何在html文件中导入header、footer等

    1.include是php函数,所以确实需要转化成.php文件--(其实除了用php,html都有自带的引入方法)2.html转化为php文件很简单,直接改一下后缀名就可以了--(如:index.ht ...

  10. Linux--4

    一.centos7安装python3 1.下载python3的源码包 下载地址:https://www.python.org/ftp/python/3.6.2/Python-3.6.2.tgz 1 2 ...