SQL优化 · 经典案例 · 索引篇

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS AI 助手,专业版
简介: Introduction在这些年的工作之中,由于SQL问题导致的数据库故障层出不穷,下面将过去六年工作中遇到的SQL问题总结归类,还原问题原貌,给出分析问题思路和解决问题的方法,帮助用户在使用数据库的过程中能够少走一些弯路。总共包括四部分:索引篇,SQL改写篇,参数优化篇,优化器篇四部分,今天将介绍第一部分:索引篇。索引问题是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都要经过严格的审核,创建合适的索引。
相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
目录
相关文章
|
6月前
|
存储 SQL 关系型数据库
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
|
3月前
|
SQL 存储 监控
SQL日志优化策略:提升数据库日志记录效率
通过以上方法结合起来运行调整方案, 可以显著地提升SQL环境下面向各种搜索引擎服务平台所需要满足标准条件下之数据库登记作业流程综合表现; 同时还能确保系统稳健运行并满越用户体验预期目标.
246 6
|
9月前
|
SQL 存储 关系型数据库
SQL优化策略与实践:组合索引与最左前缀原则详解
本文介绍了SQL优化的多种方式,包括优化查询语句(避免使用SELECT *、减少数据处理量)、使用索引(创建合适索引类型)、查询缓存、优化表结构、使用存储过程和触发器、批量处理以及分析和监控数据库性能。同时,文章详细讲解了组合索引的概念及其最左前缀原则,即MySQL从索引的最左列开始匹配条件,若跳过最左列,则索引失效。通过示例代码,展示了如何在实际场景中应用这些优化策略,以提高数据库查询效率和系统响应速度。
394 10
|
9月前
|
SQL 数据库 数据安全/隐私保护
数据库数据恢复——sql server数据库被加密的数据恢复案例
SQL server数据库数据故障: SQL server数据库被加密,无法使用。 数据库MDF、LDF、log日志文件名字被篡改。 数据库备份被加密,文件名字被篡改。
|
11月前
|
SQL 关系型数据库 MySQL
MySQL进阶突击系列(07) 她气鼓鼓递来一条SQL | 怎么看执行计划、SQL怎么优化?
在日常研发工作当中,系统性能优化,从大的方面来看主要涉及基础平台优化、业务系统性能优化、数据库优化。面对数据库优化,除了DBA在集群性能、服务器调优需要投入精力,我们研发需要负责业务SQL执行优化。当业务数据量达到一定规模后,SQL执行效率可能就会出现瓶颈,影响系统业务响应。掌握如何判断SQL执行慢、以及如何分析SQL执行计划、优化SQL的技能,在工作中解决SQL性能问题显得非常关键。
|
10月前
|
SQL 索引
【YashanDB知识库】字段加上索引后,SQL查询不到结果
【YashanDB知识库】字段加上索引后,SQL查询不到结果
|
8月前
|
SQL 存储 自然语言处理
SQL的解析和优化的原理:一条sql 执行过程是什么?
SQL的解析和优化的原理:一条sql 执行过程是什么?
SQL的解析和优化的原理:一条sql 执行过程是什么?
|
9月前
|
关系型数据库 MySQL 大数据
大数据新视界--大数据大厂之MySQL 数据库课程设计:MySQL 数据库 SQL 语句调优的进阶策略与实际案例(2-2)
本文延续前篇,深入探讨 MySQL 数据库 SQL 语句调优进阶策略。包括优化索引使用,介绍多种索引类型及避免索引失效等;调整数据库参数,如缓冲池、连接数和日志参数;还有分区表、垂直拆分等其他优化方法。通过实际案例分析展示调优效果。回顾与数据库课程设计相关文章,强调全面认识 MySQL 数据库重要性。为读者提供综合调优指导,确保数据库高效运行。
|
10月前
|
SQL 关系型数据库 MySQL
如何优化SQL查询以提高数据库性能?
这篇文章以生动的比喻介绍了优化SQL查询的重要性及方法。它首先将未优化的SQL查询比作在自助餐厅贪多嚼不烂的行为,强调了只获取必要数据的必要性。接着,文章详细讲解了四种优化策略:**精简选择**(避免使用`SELECT *`)、**专业筛选**(利用`WHERE`缩小范围)、**高效联接**(索引和限制数据量)以及**使用索引**(加速搜索)。此外,还探讨了如何避免N+1查询问题、使用分页限制结果、理解执行计划以及定期维护数据库健康。通过这些技巧,可以显著提升数据库性能,让查询更高效流畅。
|
11月前
|
SQL 关系型数据库 MySQL
基于SQL Server / MySQL进行百万条数据过滤优化方案
对百万级别数据进行高效过滤查询,需要综合使用索引、查询优化、表分区、统计信息和视图等技术手段。通过合理的数据库设计和查询优化,可以显著提升查询性能,确保系统的高效稳定运行。
588 9