一篇文章聊透索引失效有哪些情况及如何解决

MySQL 索引失效是篇文一个相当普遍的问题,尤其在处理慢查询时特别需要注意是章聊否存在索引失效的情况。

排查索引失效问题的透索第一步,必须定位要分析的引失 SQL 语句,然后通过EXPLAIN来查看其执行计划。情况主要关注type、及何解决key和extra这几个字段。篇文

具体需要关注的章聊字段可参考文章:分析 SQL 执行计划,需要关注哪些重要信息

我们需根据 key、透索type 和 extra 判断 SQL 查询是引失否利用了索引。若是情况,是及何解决否为覆盖索引、索引下推、篇文整体索引扫描,章聊或是透索索引跳跃扫描等情况。

通常,优化的索引使用情况包括以下几种:

首先,key 字段必须有值,不得为 NULL。其次,type 应该是 ref、免费源码下载eq_ref、range、const 等几种类型。此外,extra 字段如果为 NULL 或者包含"using index"、"using index condition"都是可以接受的情况。

如果执行计划显示一条 SQL 语句没有有效利用索引,例如 type = ALL,key = NULL,extra = Using where。

那么就需要进一步分析未能有效利用索引的原因。需要了解的是,是否需要使用索引以及应该使用哪个索引,这是由 MySQL 的优化器决定的,它会根据成本估算做出决策。

以下是导致未能有效利用索引的几种可能情况:

索引未正确创建:当查询语句中的 where 条件涉及的字段未创建索引,或者索引未满足最左前缀匹配条件时,就未能正确创建索引。索引区分度不足:如果索引的区分度不高,服务器租用可能导致未使用索引,因为在这种情况下,利用索引并不能有效提升查询效率。表过小:当表中的数据量很少时,优化器可能认为全表扫描的成本不高,因此可能选择不使用索引。查询语句中使用了函数或字段类型不匹配等情况导致索引失效。

这时候我们需要从头开始逐一分析:

如果索引未正确创建,根据 SQL 语句创建适当的索引。如果未遵守最左前缀原则,调整索引或修改 SQL 语句。若索引区分度不高,考虑选择另一个更合适的索引字段。对于表过小的情况,优化影响可能不大,因此是否使用索引可以不做过多优化。排查具体的失效原因,然后有针对性地调整 SQL 语句。可能导致索引失效的情况

假设我们有一张表(以下 SQL 实验基于 MySQL 5.7):

复制CREATE TABLE `mytable` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `age` int(11) DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`), KEY `age` (`age`), KEY `create_time` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; insert into mytable(id,name,age,create_time) values (1,"paidaxing",20,now()); insert into mytable(id,name,age,create_time) values (2,"paidaxing1",21,now()); insert into mytable(id,name,age,create_time) values (3,"paidaxing2",22,now()); insert into mytable(id,name,age,create_time) values (4,"paidaxing3",20,now()); insert into mytable(id,name,age,create_time) values (5,"paidaxing4",14,now()); insert into mytable(id,name,age,create_time) values (6,"paidaxing5",43,now()); insert into mytable(id,name,age,create_time) values (7,"paidaxing6",32,now()); insert into mytable(id,name,age,create_time) values (8,"paidaxing7",12,now()); insert into mytable(id,name,age,create_time) values (9,"paidaxing8",1,now()); insert into mytable(id,name,age,create_time) values (10,"paidaxing9",43,now());1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21. 索引列参与计算 复制select * from mytable where age = 12; +----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-------+ | 1 | SIMPLE | mytable | NULL | ref | age | age | 5 | const | 1 | 100.00 | NULL | +----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-------+1.2.3.4.5.6.7.

以上 SQL 语句是可以利用索引的,但如果在字段中增加计算操作,就可能导致索引失效:

复制select * from mytable where age +1 = 12; +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | mytable | NULL | ALL | NULL | NULL | NULL | NULL | 10 | 100.00 | Using where | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+1.2.3.4.5.6.7.

但如果计算的形式如下,仍然可以利用索引:

复制select * from mytable where age = 12 - 1; +----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-------+ | 1 | SIMPLE | mytable | NULL | ref | age | age | 5 | const | 1 | 100.00 | NULL | +----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-------+1.2.3.4.5.6.7. 对索引列进行函数操作 复制SELECT * FROM mytable WHERE create_time = 2023-04-01 00:00:00; +----+-------------+---------+------------+------+---------------+-------------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+-------------+---------+-------+------+----------+-------+ | 1 | SIMPLE | mytable | NULL | ref | create_time | create_time | 6 | const | 1 | 100.00 | NULL | +----+-------------+---------+------------+------+---------------+-------------+---------+-------+------+----------+-------+1.2.3.4.5.6.7.

以上 SQL 语句可以利用索引,免费信息发布网但如果在字段中添加函数操作,可能会导致索引失效:

复制SELECT * FROM mytable WHERE YEAR(create_time) = 2022; +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | mytable | NULL | ALL | NULL | NULL | NULL | NULL | 10 | 100.00 | Using where | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+1.2.3.4.5.6.7. 使用 OR 复制SELECT * FROM mytable WHERE name = paidaxing and age > 18; +----+-------------+---------+------------+-------+---------------+------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+-------+---------------+------+---------+-------+------+----------+-------+ | 1 | SIMPLE | mytable | NULL | const | name,age | name | 202 | const | 1 | 100.00 | NULL | +----+-------------+---------+------------+-------+---------------+------+---------+-------+------+----------+-------+1.2.3.4.5.6.7.

但如果使用 OR,并且 OR 条件中的两侧包含<或者>操作符时,可能会导致索引失效,例如:

复制SELECT * FROM mytable WHERE name = paidaxing OR age > 18; +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | mytable | NULL | ALL | name,age | NULL | NULL | NULL | 10 | 40.00 | Using where | +-1.2.3.4.5.6.7.

但如果 OR 条件的两侧都是等号判断,并且两个字段都有索引,仍然可以利用索引,例如:

复制mysql> explain SELECT * FROM mytable WHERE name = paidaxing OR age = 18; +----+-------------+---------+------------+-------------+---------------+----------+---------+------+------+----------+------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+-------------+---------------+----------+---------+------+------+----------+------------------------------------+ | 1 | SIMPLE | mytable | NULL | index_merge | name,age | name,age | 202,5 | NULL | 2 | 100.00 | Using union(name,age); Using where | +----+-------------+---------+------------+-------------+---------------+----------+---------+------+------+----------+------------------------------------+1.2.3.4.5.6. like 操作 复制SELECT * FROM mytable WHERE nick like %paidaxing%; +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | mytable | NULL | ALL | NULL | NULL | NULL | NULL | 10 | 11.11 | Using where | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ SELECT * FROM mytable WHERE nick like %paidaxing; +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | mytable | NULL | ALL | NULL | NULL | NULL | NULL | 10 | 11.11 | Using where | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ SELECT * FROM mytable WHERE nick like paidaxing%; +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | mytable | NULL | range| nick | nick | NULL | NULL | 10 | 100.00 | Using index condition | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ SELECT * FROM mytable WHERE nick like paida%xing; +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | mytable | NULL | range| nick | nick | NULL | NULL | 10 | 100.00 | Using index condition | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.

以上四种 LIKE 模式中,"paidaxing%"和"paida%xing"这两种可以利用索引,但是如果是"%paidaxing%"和"%paidaxing"就无法利用索引。

隐式类型转换 复制select * from mytable where name = 1; +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | mytable | NULL | ALL | name | NULL | NULL | NULL | 10 | 10.00 | Using where | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+1.2.3.4.5.6.

以上情况中,如果表中的 name 字段是 varchar 类型,但我们使用 int 类型进行查询,这会导致索引失效。

然而,有一个特例是,如果字段类型是 int 类型,而查询条件中添加了单引号或双引号,MySQL 会将参数转换为 int 类型,这种情况下也可以利用索引。

复制select * from mytable where age = 1; +----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-------+ | 1 | SIMPLE | mytable | NULL | ref | age | age | 5 | const | 1 | 100.00 | NULL | +----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-------+1.2.3.4.5.6.7. 不等于比较 复制SELECT * FROM mytable WHERE age != 18; +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | mytable | NULL | ALL | age | NULL | NULL | NULL | 10 | 100.00 | Using where | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+1.2.3.4.5.6.7.

但并非所有情况都是如此,例如在以下情况下,使用 ID 进行!=比较时,可能会利用索引:

复制SELECT * FROM mytable WHERE id != 18; +----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | SIMPLE | mytable | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 12 | 100.00 | Using where | +----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+1.2.3.4.5.6.

在使用!=(不等于)操作符时,索引是否失效与多个因素有关,包括索引的选择性、数据的分布情况等,并不能简单地因为使用了!=操作符就说导致了索引失效。

is not null

以下情况是索引失效的:

复制SELECT * FROM mytable WHERE name is not null +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | mytable | NULL | ALL | name | NULL | NULL | NULL | 10 | 90.00 | Using where | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+1.2.3.4.5.6.7. order by 复制SELECT * FROM mytable order by age +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+----------------+ | 1 | SIMPLE | mytable | NULL | ALL | NULL | NULL | NULL | NULL | 10 | 100.00 | Using filesort | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+----------------+1.2.3.4.5.6.7.

当进行 ORDER BY 操作时,如果数据量非常小,数据库可能会选择在内存中进行排序,而不是使用索引。

in

使用 IN 操作时,有时会走索引,有时则不会。一般来说,当 IN 子句中的值较少时,数据库可能会选择使用索引进行优化;但如果 IN 子句中的选项较多,可能就不会使用索引。

复制mysql> explain select * from mytable where name in ("paidaxing"); +----+-------------+---------+------------+-------+---------------+------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+-------+---------------+------+---------+-------+------+----------+-------+ | 1 | SIMPLE | mytable | NULL | const | name | name | 202 | const | 1 | 100.00 | NULL | +----+-------------+---------+------------+-------+---------------+------+---------+-------+------+----------+-------+ mysql> explain select * from mytable where name in ("paidaxing","pdx"); +----+-------------+---------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+ | 1 | SIMPLE | mytable | NULL | range | name | name | 202 | NULL | 2 | 100.00 | Using index condition | +----+-------------+---------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+ mysql> explain select * from mytable where name in ("paidaxing","pdx","x"); +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | mytable | NULL | ALL | name | NULL | NULL | NULL | 11 | 27.27 | Using where | +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.

好了,本章节到此告一段落。希望对你有所帮助,祝学习顺利。

探索
上一篇:MacBookAir(轻薄便携,高性能表现,适用于多种场景)
下一篇:尽管Apache已经占据半片江山,但很多人仍然在寻找其他的方式去托管他们的站点,Apache不只是一个选择,其他很多优秀的服务器程序例如lighthttp和nginx也是不错的选择。本教程将要向您展现如何在Ubuntu操作系统上面安装,教程同样适用在Debian,尽管有一点点小差别,但并没有太大影响。怎么样,下面我们开始吧。0.开始注意为了完成教程中提到的操作,我们假设您已经安装了一个基本的Debian或者Ubuntu操作系统。怎么安装系统这是不同的教程了,这里就不再详细说明。本教程主要介绍如何简单获取Nginx+php的运行环境。1.安装Nginx第一步要做的就是从库中下载,这个操作是非常简单的。sudo apt-get install nginx更改默认的虚拟站点配置,文件在:sudo vim /etc/nginx/sites-available/default一个漂亮的关键配置是:server { listen 80; server_name localhost; access_log /var/log/nginx/localhost.access.log;## Default location location / { root /var/www; index index.php; }## Images and static content is treated different location ~* ^.+.(jpg|jpeg|gif|css|png|js|ico|xml)$ { access_log off; expires 30d; root /var/www; }## Parse all .php file in the /var/www directory location ~ .php$ { fastcgi_split_path_info ^(.+.php)(.*)$; fastcgi_pass backend; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /var/www$fastcgi_script_name; include fastcgi_params; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_intercept_errors on; fastcgi_ignore_client_abort off; fastcgi_connect_timeout 60; fastcgi_send_timeout 180; fastcgi_read_timeout 180; fastcgi_buffer_size 128k; fastcgi_buffers 4 256k; fastcgi_busy_buffers_size 256k; fastcgi_temp_file_write_size 256k; }## Disable viewing .htaccess & .htpassword location ~ /.ht { deny all; }}upstream backend { server 127.0.0.1:9000;} 好了,我们完成到这里,下面我们需要安装PHP所需要的文件。2.安装PHP很多站点都使用PHP提供动态内容,他们可能是一个wiki一个博客,或者一个论坛什么的。假如你现在运行的是Ubuntu,我们首先需要解决两种不同的deb包,假如你运行的是amd64版本,那需要更改i386为amd64注意:假如是debian则不需要做这些cd /tmpwget http://us.archive.ubuntu.com/ubuntu/pool/main/k/krb5/libkrb53_1.6.dfsg.4~beta1-5ubuntu2_i386.debwget http://us.archive.ubuntu.com/ubuntu/pool/main/i/icu/libicu38_3.8-6ubuntu0.2_i386.debsudo dpkg -i *.deb再次说明,这个只需要在Ubuntu上面操作就可以了,假如是最新版本的12.04可以省略掉这个。下面剩余部分将在Ubuntu和Debian上面都通用。我们需要增加APT源来增加deb包,所以我们可以使用php5.3和php-fpmsudo echo deb http://php53.dotdeb.org stable all >>/etc/apt/sources.list更新apt:sudo apt-get update下面我们开始安装PHP(第一部分)sudo apt-get install php5-cli php5-common php5-suhosin我们需要安装命令行,不然接下来就会出现些小问题sudo apt-get install php5-fpm php5-cgi假如你打算使用数据库或者一些需要的模块(例如:mcrypt,ldap,snmp等)你同样可以安装他们。好了,我们现在已经安装完了nginx和php一个注意点:假如你使用php的短标记(< ),你需要在php.ini中开启(fpm和cli同样),假如你不改变这些,你将看到代码以文本的方式显示。3.完成重启nginx服务器sudo /etc/init.d/nginx restart配置完成php.ini后需要重启php5-fpm,使用命令sudo /etc/init.d/php5-fpm restart全部完成,你可以运行了。4.测试我们可以在/var/www中创建一个index.php的文件,内容如下:< php phpinfo(); >通过浏览器访问,你可以看到php的一个大致的信息,假如没有则说明一些配置错误,需要重新检查。5.问题和最终提示假如你没有看到phpinfo,可能会出现一些错误,那可以执行:sudo tail /var/log/nginx/error.log查看错误日志,另外每次当你修改了php.ini后,需要重启php5-fpm。无需重启nginx。在默认的配置中nginx是启用了fastcgi的错误日志的,假如看到一个严重的错误(例如出现cannot redeclare class xyz),nginx可以看到这个“漂亮的”错误页面,那可能是一些错误发生了。假如这样你可以关闭,假如php5-fpm不能运行,你的php文件将不会被解析,并且nginx会显示一个错误页面。好了,我猜你已经完成了,并且现在使用nginx作为你的web服务器,nginx是一个小巧、漂亮并且你可以配置很多东西的一个东东,假如你需要伪静态,注意nginx不会运行 .htaccess 文件,你需要伪静态更改vhost配置。假如对在Linux下感兴趣的朋友可以查看《Linux下的DedeCMS站点高级安全策略》翻译:天涯  原文:http://www.howtoforge.com/installing-php-5.3-nginx-and-php-fpm-on-ubuntu-debian