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 性能低,执行时间 ...
随机推荐
- URAL 2018 The Debut Album (DP)
题意:给出n长度的数列,其实1的连续个数不超过a,2的连续个数不超过b. 析:dp[i][j][k] 表示前 i 个数,以 j 结尾,并且连续了k个长度,要用滚动数组,要不然MLE. 代码如下: #p ...
- 【转】Lucene不同版本中Field的Keyword、UnIndex,导致lucene 建立索引总是报错 急!!
lucene 建立索引 总是报错 急!! http://zhidao.baidu.com/link?url=iaVs9JH4DfN6iwaWImt7VMJENWCWGGaWFGPjqhUw_jz7Fs ...
- hdu1047
#include<stdio.h>#include<string>#include<iostream>using namespace std; //高精度加法//只 ...
- 【Android-stdio-appdemo搭建记录】
1-如何删除存在的工程 2-创建Android项目 next设置app兼容最低版本:api15--android 4.0以上 创建活动页面 设置活动页面的名称 创建成功项目以后会有最基本的layout ...
- 机器学习十大算法之EM算法
此文已由作者赵斌授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 由于目前论坛的Markdown不支持Mathjax,数学公式没法正常识别,文章只能用截图上传了... ...
- 详细讲解MapReduce二次排序过程
我在15年处理大数据的时候还都是使用MapReduce, 随着时间的推移, 计算工具的发展, 内存越来越便宜, 计算方式也有了极大的改变. 到现在再做大数据开发的好多同学都是直接使用spark, hi ...
- Codeforces Round #520 (Div. 2)B(贪心,数学)
#include<bits/stdc++.h>using namespace std;int mi[100007];int main(){ int cnt=0; int flag=0; i ...
- 洛谷P1311 选择客栈
P1311 选择客栈 题目描述 丽江河边有n 家很有特色的客栈,客栈按照其位置顺序从 1 到n 编号.每家客栈都按照某一种色调进行装饰(总共 k 种,用整数 0 ~ k-1 表示),且每家客栈都设有一 ...
- kali源
apt源: #中科大 deb http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib deb-src http://m ...
- AngularJS模块——module
angular.module('myApp',[]) 1.定义模块 2.第一个参数:定义的模块名: 3.第二个参数:依赖列表,也就是可以被注入到模块中的对象列表:依赖的这些模块需要在本模块加载之前由注 ...