MySQL慢查询优化最佳实践(一)

慢查询

我们知道,一般的应用系统,MySQL的读写比例在10:1左右,而且一般的插入和更新操作很少会出现性能问题,遇到问题最多的,也是最容易出现问题的,还是一些复杂的查询操作,所以查询语句的优化已经成为开发、运维工程师们的必须课,是大部分运维工作之中的重中之重。

索引原理

数据库建立索引的目的是为了提高查询效率,索引如同生活中的字典、列车时刻表、图书目录一样,原理都相同,都是通过不断缩小想要获得的数据范围来筛选出最终想要的结果,把本来是随机查询的事件变成有序的事件,如我们在字典里查“mysql”单词,如果不建立索引,得从头翻到尾,想想是不是很恐怖!而我们会先选择查询m开头再查y字母范围内的单词,最后一个个定位到mysql。

索引建立的深层原理涉及到磁盘的IO与预读、索引的数据结构b+树、b+树的性质和查找过程等等...

b+查询过程

如图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高

建立索引的几大原则

1)最左前缀匹配原则:mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整;

2)索引数量并不是越多越好,索引也要占用空间,且没增加数据都要维护索引,尽量扩展索引,不要新建索引;

3)在唯一值多的大表上建立索引,唯一值的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,如select count(distinct user) from mysql.user;

慢查询相关设置

1)定义慢查询时间

mysql> set long_query_time=0.001
#一般看业务系统定义在1秒以上,这里我们方便测试,这是为0.001秒

修改配置文件

[mysqld]
long_query_time = 0.001
mysql> show variables like ‘%long_qu%‘;
+-----------------+----------+
| Variable_name   | Value    |
+-----------------+----------+
| long_query_time | 0.001000 |
+-----------------+----------+
1 row in set (0.00 sec)

2)开启慢查询日志

mysql> set GLOBAL slow_query_log=ON; #设置全局变量
mysql> show variables like ‘%slow%‘;
+---------------------+-------------------------------+
| Variable_name       | Value                         |
+---------------------+-------------------------------+
| log_slow_queries    | ON                            |
| slow_launch_time    | 2                             |
| slow_query_log      | ON                            |
| slow_query_log_file | /data/3306/data/db02-slow.log |
+---------------------+-------------------------------+
4 rows in set (0.00 sec)

查询优化神器--explain

explain简单地说就是能够模拟查询过程,分析使用到的和可能使用到的索引列,及最终查询的rows数,通过降低核心指标rows数起到对mysql查询语句优化的结果,当然记得要SQL_NO_CACHE。

1)我们模拟插入一组数据

mysql> CREATE DATABASE lichengbing;
mysql> USE lichengbing;
mysql> CREATE TABLE `user_name` (
  `id` int(4) NOT NULL AUTO_INCREMENT,
  `name` char(20) NOT NULL,
  `age` tinyint(2) NOT NULL DEFAULT ‘0‘,
  `dept` varchar(16) DEFAULT NULL,
  PRIMARY KEY (`id`) #注意,我们只是创建了一个主键,没有创建任何索引列
) ENGINE=InnoDB DEFAULT CHARSET=utf8

用脚本批量插入数据

#!/bin/sh
for((i=1;i<=100000;i++))
do
dept=\‘IT$i\‘
age=`echo $RANDOM|cut -c 1-2`
mysql  -S /data/3306/mysql.sock -e "insert into lichengbing.user_name(name,age,dept) values(‘Kobe‘,$age,$dept);"
done

2)我们来看看在没有索引情况下查询速度和explain结果

mysql> explain select * from lichengbing.user_name where name=‘kobe‘ and age < 20\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user_name
         type: ALL
possible_keys: NULL #显示没有使用到任何可能的索引
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 71761 #MySQL由于没有使用到索引,扫描了全部的71761条数据
        Extra: Using where
1 row in set (0.00 sec)

此时我们在慢查询日志里面就能查看到刚才执行的慢查询操作语句

[[email protected] data]# cat db02-slow.log 
/application/mysql-5.5.32/bin/mysqld, Version: 5.5.32-log (Source distribution). started with:
Tcp port: 3306  Unix socket: /data/3306/mysql.sock
Time                 Id Command    Argument
# Time: 160728  8:01:30
# [email protected]: root[root] @ localhost []
# Query_time: 0.010430  Lock_time: 0.000000 Rows_sent: 0  Rows_examined: 0
SET timestamp=1469664090;
set GLOBAL slow_query_log=ON;
# Time: 160728  8:31:02
# [email protected]: root[root] @ localhost []
# Query_time: 0.009233  Lock_time: 0.000000 Rows_sent: 0  Rows_examined: 0
SET timestamp=1469665862;
select * from lichengbing.user_name where name=‘kobe‘ and age < 20;

或者,在生产环境中,如果有大于1秒的慢查询导致应用程序响应缓慢,我们可以这样查看

[[email protected] ~]# mysql -S /data/3306/mysql.sock -e "show full processlist"
+-------+------+-----------+------+---------+------+-------+-----------------------+
| Id    | User | Host      | db   | Command | Time | State | Info                  |
+-------+------+-----------+------+---------+------+-------+-----------------------+
| 68928 | root | localhost | NULL | Sleep   |  185 |       | NULL                  |
| 69629 | root | localhost | NULL | Query   |    0 | NULL  | show full processlist |
+-------+------+-----------+------+---------+------+-------+-----------------------+

3)添加索引,优化查询

我们先来看看给age添加索引效果

mysql> desc user_name;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(4)      | NO   | PRI | NULL    | auto_increment |
| name  | char(20)    | NO   |     | NULL    |                |
| age   | tinyint(2)  | NO   |     | 0       |                | #此时没有任何索引
| dept  | varchar(16) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
mysql> alter table user_name add  index index_age(age); #以表age列创建索引
mysql> desc user_name;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(4)      | NO   | PRI | NULL    | auto_increment |
| name  | char(20)    | NO   |     | NULL    |                |
| age   | tinyint(2)  | NO   | MUL | 0       |                |#索引创建成功
| dept  | varchar(16) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
mysql> explain select SQL_NO_CACHE * from lichengbing.user_name where age=‘20‘\G;
*************************** 1. row ***************************
           id: 1 #jSQL_NO_CACHE
  select_type: SIMPLE
        table: user_name
         type: ref
possible_keys: index_age
          key: index_age
      key_len: 1
          ref: const
         rows: 2417 #添加索引后只扫描了2417行,大大减少了查询时间
        Extra: 
1 row in set (0.00 sec)

4)我们再来看看创建联合索引

mysql> alter table user_name drop  index index_name; #删除掉之前创建的索引
mysql> alter table user_name drop  index index_age;
mysql> create index index_name_age on lichengbing.uer_name(name,age); #创建联合索引
mysql> desc lichengbing.user_name;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(4)      | NO   | PRI | NULL    | auto_increment |
| name  | char(20)    | NO   | MUL | NULL    |                |
| age   | tinyint(2)  | NO   |     | 0       |                |
| dept  | varchar(16) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
mysql> show index from lichengbing.user_name\G;
*************************** 1. row ***************************
        Table: user_name
   Non_unique: 0
     Key_name: PRIMARY #主键
 Seq_in_index: 1
  Column_name: id
    Collation: A
  Cardinality: 71761
     Sub_part: NULL
       Packed: NULL
         Null: 
   Index_type: BTREE
      Comment: 
Index_comment: 
*************************** 2. row ***************************
        Table: user_name
   Non_unique: 1
     Key_name: index_name_age #索引age
 Seq_in_index: 1
  Column_name: name
    Collation: A
  Cardinality: 199
     Sub_part: NULL
       Packed: NULL
         Null: 
   Index_type: BTREE
      Comment: 
Index_comment: 
*************************** 3. row ***************************
        Table: user_name
   Non_unique: 1
     Key_name: index_name_age #索引name
 Seq_in_index: 2
  Column_name: age
    Collation: A
  Cardinality: 199
     Sub_part: NULL
       Packed: NULL
         Null: 
   Index_type: BTREE
      Comment: 
Index_comment: 
3 rows in set (0.00 sec)

查看一下联合索引效果

mysql> explain select SQL_NO_CACHE * from lichengbing.user_name where age=‘20‘ and name=‘Anthony‘\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user_name
         type: ref
possible_keys: index_name_age
          key: index_name_age
      key_len: 61
          ref: const,const
         rows: 87 #扫描了81条正确数据,done!
        Extra: Using where
1 row in set (0.00 sec)

索引列的选择和生效条件

1)并不是建立的索引越多越好,索引也要占用空间,且没增加数据都要维护索引

2)索引尽量在唯一值多的大表上建立索引

查看索引唯一值方法

mysql> select count(distinct id)from lichengbing.user_name;
+--------------------+
| count(distinct id) |
+--------------------+
|              71388 | #ID列我们不需要select
+--------------------+
1 row in set (0.08 sec)
mysql> select count(distinct name)from lichengbing.user_name;
+----------------------+
| count(distinct name) |
+----------------------+
|                    7 |
+----------------------+
1 row in set (0.00 sec)
mysql> select count(distinct age)from lichengbing.user_name;
+---------------------+
| count(distinct age) |
+---------------------+
|                  97 | #相比其他列,在age列创建索引较好
+---------------------+
1 row in set (0.02 sec)
时间: 2024-09-29 23:30:46

MySQL慢查询优化最佳实践(一)的相关文章

MySQL &#183; 答疑解惑 &#183; MySQL 锁问题最佳实践

http://mysql.taobao.org/monthly/2016/03/10/ 前言 最近一段时间处理了较多锁的问题,包括锁等待导致业务连接堆积或超时,死锁导致业务失败等,这类问题对业务可能会造成严重的影响,没有处理经验的用户往往无从下手.下面将从整个数据库设计,开发,运维阶段介绍如何避免锁问题的发生,提供一些最佳实践供RDS的用户参考. 设计阶段 在数据库设计阶段,引擎选择和索引设计不当可能导致后期业务上线后出现较为严重的锁或者死锁问题. 1. 表引擎选择使用myisam,引发tabl

中国移动MySQL数据库优化最佳实践

原创 2016-08-12 章颖 DBAplus社群 本文根据DBAplus社群第69期线上分享整理而成,文末还有书送哦~ 讲师介绍章颖 数据研发工程师 现任中国移动杭州研发中心数据研发工程师,擅长MySQL故障诊断,性能调优,MySQL高可用技术,曾任中国电信综合平台开发运营中心DBA 开源数据库MySQL比较容易碰到性能瓶颈,为此经常需要对MySQL数据库进行优化,而MySQL数据库优化需要运维DBA与相关开发共同参与,其中MySQL参数及服务器配置优化主要由运维DBA完成,开发则需要从数据

MySQL面试必考知识点:揭秘亿级高并发数据库调优与最佳实践法则

做业务,要懂基本的SQL语句: 做性能优化,要懂索引,懂引擎: 做分库分表,要懂主从,懂读写分离... 数据库的使用,是开发人员的基本功,对它掌握越清晰越深入,你能做的事情就越多. 今天我们用10分钟,重点梳理一遍以下几方面: 数据库知识点汇总: 数据库事务特性和隔离级别: 详解关系型数据库.索引与锁机制: 数据库调优与最佳实践: 面试考察点及加分项. 知识点汇总 一.数据库的不同类型 1.常用的关系型数据库 Oracle:功能强大,主要缺点就是贵 MySQL:互联网行业中最流行的数据库,这不仅

[转]10分钟梳理MySQL知识点:揭秘亿级高并发数据库调优与最佳实践法则

转:https://mp.weixin.qq.com/s/RYIiHAHHStIMftQT6lQSgA 做业务,要懂基本的SQL语句: 做性能优化,要懂索引,懂引擎: 做分库分表,要懂主从,懂读写分离... 数据库的使用,是开发人员的基本功,对它掌握越清晰越深入,你能做的事情就越多. 今天我们用10分钟,重点梳理一遍以下几方面: 数据库知识点汇总: 数据库事务特性和隔离级别: 详解关系型数据库.索引与锁机制: 数据库调优与最佳实践: 面试考察点及加分项. 一.数据库的不同类型 1.常用的关系型数

MySQL性能优化的21个最佳实践

今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显.关于数据库的性能,这并不只是DBA才需要担心的事,而这更是我们程序员需要去关注的事情.当我们去设计数据库表结构,对操作数据库时(尤其是查表时的SQL语句),我们都需要注意数据操作的性能.这里,我们不会讲过多的SQL语句的优化,而只是针对MySQL这一Web应用最多的数据库.希望下面的这些优化技巧对你有用. 1. 为查询缓存优化你的查询 大多数的MySQL服务器都开启了查询缓存.这是提高性最有效的方法之一,而且这是被My

【MySQL】锁问题最佳实践

最近一段时间处理了较多锁的问题,包括锁等待导致业务连接堆积或超时,死锁导致业务失败等,这类问题对业务可能会造成严重的影响,没有处理经验的用户往往无从下手.下面将从整个数据库设计,开发,运维阶段介绍如何避免锁问题的发生,提供一些最佳实践供读者参考. 设计阶段 在数据库设计阶段,引擎选择和索引设计不当可能导致后期业务上线后出现较为严重的锁或者死锁问题. 1. 表引擎选择使用myisam,引发table level lock wait. 从5.5版本开始,MySQL官方就把默认引擎由myisam转为i

ySQL性能优化的21个最佳实践 和 mysql使用索引

MySQL性能优化的21个最佳实践 和 mysql使用索引 今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显.关于数据库的性能,这并不只是DBA才需要担心的事,而这更是我 们程序员需要去关注的事情.当我们去设计数据库表结构,对操作数据库时(尤其是查表时的SQL语句),我们都需要注意数据操作的性能.这里,我们不会讲过 多的SQL语句的优化,而只是针对MySQL这一Web应用最多的数据库.希望下面的这些优化技巧对你有用. 1. 为查询缓存优化你的查询 大多数的MySQL服

MySQL与OLAP:分析型SQL查询最佳实践探索

搞点多维分析,糙快猛的解决方案就是使用ROLAP(关系型OLAP)了.数据经维度建模后存储在MySQL,ROLAP引擎(比如开源的Mondrian)负责将OLAP请求转化为SQL语句提交给数据库.OLAP计算分析功能导致MySQL需要进行较多复杂SQL查询,性能调优必不可少,本文总结了一些实用原则. OLAP特点 OLAP的典型应用包括复杂动态报表,需要支持钻取(上卷和下钻).切片.切块和旋转操作.下表总结了OLAP和OLTP系统的主要区别.OLAP的特点决定了SQL的查询场景和优化方案,下文将

MySQL 数据库设计之各种 INTEGER 类型最佳实践

MySQL 各种 INTEGER 类型占用存储空间.取值范围一览表最佳实践 UNSIGNED 只能存储非负整数 SIGNED 可以存储正整数.0.负整数 对于总是正整数的存储(比如主键)的最佳实践是 UNSIGNED,因为这时它占用和 SIGNED 一样的存储空间,但取值范围多出一倍 BOOL.BOOLEAN 只不过是 TINYINT(1) 的另外一种写法而已 TINYINT(1).BOOL.BOOLEAN 所占用的存储空间和 TINYINT 一样,都是一个字节,而不是一位 TINYINT(1)