Skip to content

MySQL 索引失效场景

索引失效概述

索引是提高MySQL查询性能的重要手段,但在某些情况下,MySQL优化器可能会选择不使用索引,导致全表扫描,从而降低查询性能。了解索引失效的常见场景,对于DBA优化查询性能至关重要。

索引失效的影响

  • 查询性能下降:全表扫描比索引扫描慢数倍甚至数十倍
  • CPU和IO资源消耗增加:全表扫描需要读取更多的数据页
  • 数据库负载升高:大量的全表扫描会导致数据库负载过高
  • 响应时间延长:影响应用程序的响应速度

索引失效的原因

  • 查询条件不符合索引设计:例如字段类型不匹配、使用函数等
  • MySQL优化器认为全表扫描更高效:例如查询数据量超过表数据的20%-30%
  • 索引统计信息不准确:导致优化器做出错误的选择
  • 索引设计不合理:例如联合索引顺序不当

如何判断索引失效

bash
# 使用 EXPLAIN 查看执行计划
EXPLAIN SELECT * FROM users WHERE name = 'test';

# 关键字段说明:
# type: 访问类型,ALL表示全表扫描,ref/eq_ref表示索引扫描
# key: 实际使用的索引
# rows: 估计需要扫描的行数
# Extra: 额外信息,例如Using index、Using where等

常见索引失效场景

字段类型不匹配

场景描述:查询条件中的字段类型与表中定义的类型不匹配,导致索引失效。

示例

sql
-- 表结构
CREATE TABLE users (
  id INT PRIMARY KEY,
  name VARCHAR(50),
  phone VARCHAR(20),
  age INT,
  INDEX idx_phone (phone)
);

-- 插入测试数据
INSERT INTO users VALUES (1, 'user1', '13800138001', 20);
INSERT INTO users VALUES (2, 'user2', '13800138002', 25);

-- 场景1:将字符串类型作为数字查询
EXPLAIN SELECT * FROM users WHERE phone = 13800138001;
-- 结果:type=ALL,全表扫描,索引失效

-- 场景2:将数字类型作为字符串查询
EXPLAIN SELECT * FROM users WHERE age = '20';
-- 结果:type=ref,索引扫描,索引有效(MySQL会自动转换)

原因分析

  • 当字符串类型字段使用数字查询时,MySQL需要将每行的字符串转换为数字,无法使用索引
  • 当数字类型字段使用字符串查询时,MySQL会将字符串转换为数字,索引仍然有效

优化建议

  • 确保查询条件中的字段类型与表中定义的类型一致
  • 使用显式类型转换:SELECT * FROM users WHERE phone = CAST(13800138001 AS CHAR);

使用函数或表达式

场景描述:在查询条件中对索引字段使用函数或表达式,导致索引失效。

示例

sql
-- 表结构
CREATE TABLE orders (
  id INT PRIMARY KEY,
  order_no VARCHAR(20),
  create_time DATETIME,
  amount DECIMAL(10,2),
  INDEX idx_create_time (create_time),
  INDEX idx_order_no (order_no)
);

-- 插入测试数据
INSERT INTO orders VALUES (1, '202301010001', '2023-01-01 10:00:00', 100.00);
INSERT INTO orders VALUES (2, '202301010002', '2023-01-01 11:00:00', 200.00);

-- 场景1:对索引字段使用函数
EXPLAIN SELECT * FROM orders WHERE DATE(create_time) = '2023-01-01';
-- 结果:type=ALL,全表扫描,索引失效

-- 场景2:对索引字段使用表达式
EXPLAIN SELECT * FROM orders WHERE id + 1 = 2;
-- 结果:type=ALL,全表扫描,索引失效

-- 场景3:对索引字段使用SUBSTRING函数
EXPLAIN SELECT * FROM orders WHERE SUBSTRING(order_no, 1, 8) = '20230101';
-- 结果:type=ALL,全表扫描,索引失效

原因分析

  • MySQL无法在索引上直接执行函数或表达式计算,需要将每行数据加载到内存中计算后比较
  • 这导致MySQL无法使用索引,只能进行全表扫描

优化建议

  • 将函数或表达式移到查询条件的右侧,或者重构查询
  • 使用计算列(generated column):ALTER TABLE orders ADD COLUMN create_date DATE GENERATED ALWAYS AS (DATE(create_time)) STORED, ADD INDEX idx_create_date (create_date);
  • 对于场景1:EXPLAIN SELECT * FROM orders WHERE create_time BETWEEN '2023-01-01 00:00:00' AND '2023-01-01 23:59:59';
  • 对于场景2:EXPLAIN SELECT * FROM orders WHERE id = 1;
  • 对于场景3:EXPLAIN SELECT * FROM orders WHERE order_no LIKE '20230101%';

模糊查询

场景描述:使用LIKE进行模糊查询时,某些情况下会导致索引失效。

示例

sql
-- 表结构
CREATE TABLE products (
  id INT PRIMARY KEY,
  name VARCHAR(50),
  category VARCHAR(20),
  price DECIMAL(10,2),
  INDEX idx_name (name),
  INDEX idx_category (category)
);

-- 插入测试数据
INSERT INTO products VALUES (1, 'iPhone 14', '手机', 5999.00);
INSERT INTO products VALUES (2, '华为 Mate 50', '手机', 6999.00);

-- 场景1:前缀模糊查询
EXPLAIN SELECT * FROM products WHERE name LIKE 'iPhone%';
-- 结果:type=range,索引扫描,索引有效

-- 场景2:后缀模糊查询
EXPLAIN SELECT * FROM products WHERE name LIKE '%14';
-- 结果:type=ALL,全表扫描,索引失效

-- 场景3:中缀模糊查询
EXPLAIN SELECT * FROM products WHERE name LIKE '%Phone%';
-- 结果:type=ALL,全表扫描,索引失效

原因分析

  • MySQL索引是B+树结构,只能从左到右匹配
  • 前缀模糊查询('%value')可以使用索引的前缀匹配
  • 后缀模糊查询('value%')和中缀模糊查询('%value%')无法使用索引,因为无法确定起始位置

优化建议

  • 尽量使用前缀模糊查询
  • 对于后缀匹配,可以考虑将字段值反转存储,并创建索引:ALTER TABLE products ADD COLUMN name_reverse VARCHAR(50), ADD INDEX idx_name_reverse (name_reverse);
  • 对于复杂的模糊查询,可以考虑使用全文索引或搜索引擎(如Elasticsearch)

OR条件

场景描述:使用OR条件连接多个查询条件时,如果其中一个条件没有索引,可能导致所有索引失效。

示例

sql
-- 表结构
CREATE TABLE users (
  id INT PRIMARY KEY,
  name VARCHAR(50),
  email VARCHAR(50),
  phone VARCHAR(20),
  INDEX idx_name (name),
  INDEX idx_email (email)
);

-- 插入测试数据
INSERT INTO users VALUES (1, 'user1', 'user1@example.com', '13800138001');
INSERT INTO users VALUES (2, 'user2', 'user2@example.com', '13800138002');

-- 场景1:OR条件中所有字段都有索引
EXPLAIN SELECT * FROM users WHERE name = 'user1' OR email = 'user1@example.com';
-- 结果:type=index_merge,使用索引合并,索引有效

-- 场景2:OR条件中部分字段没有索引
EXPLAIN SELECT * FROM users WHERE name = 'user1' OR phone = '13800138001';
-- 结果:type=ALL,全表扫描,索引失效

原因分析

  • 当OR条件中所有字段都有索引时,MySQL可以使用索引合并(index_merge)
  • 当OR条件中部分字段没有索引时,MySQL无法使用索引,只能进行全表扫描

优化建议

  • 为OR条件中的所有字段创建索引
  • 将OR条件拆分为多个UNION查询:SELECT * FROM users WHERE name = 'user1' UNION SELECT * FROM users WHERE phone = '13800138001';
  • 考虑使用其他条件替代OR,例如IN、EXISTS等

范围查询

场景描述:使用范围查询(>、<、>=、<=、BETWEEN)时,可能导致后续索引失效。

示例

sql
-- 表结构
CREATE TABLE orders (
  id INT PRIMARY KEY,
  user_id INT,
  order_date DATETIME,
  amount DECIMAL(10,2),
  INDEX idx_user_id_order_date (user_id, order_date, amount)
);

-- 插入测试数据
INSERT INTO orders VALUES (1, 1, '2023-01-01', 100.00);
INSERT INTO orders VALUES (2, 1, '2023-01-02', 200.00);

-- 场景1:范围查询在前,后续索引失效
EXPLAIN SELECT * FROM orders WHERE user_id = 1 AND order_date > '2023-01-01' AND amount > 150;
-- 结果:key=idx_user_id_order_date,但是只使用了user_id和order_date索引,amount索引失效

-- 场景2:范围查询在后,所有索引有效
EXPLAIN SELECT * FROM orders WHERE user_id = 1 AND amount > 150 AND order_date > '2023-01-01';
-- 结果:key=idx_user_id_order_date,但是优化器会重新排序条件,同样只使用了user_id和order_date索引

原因分析

  • 联合索引的生效顺序是从左到右
  • 当遇到范围查询时,MySQL无法确定范围查询后的字段值,导致后续索引失效
  • MySQL优化器会自动排序查询条件,将等值条件放在前面,范围条件放在后面

优化建议

  • 将范围查询字段放在联合索引的最后
  • 对于需要多个范围查询的场景,考虑创建多个索引,让MySQL使用索引合并
  • 使用覆盖索引,减少回表操作

NOT IN / NOT EXISTS

场景描述:使用NOT IN或NOT EXISTS时,可能导致索引失效。

示例

sql
-- 表结构
CREATE TABLE orders (
  id INT PRIMARY KEY,
  user_id INT,
  status INT,
  INDEX idx_user_id (user_id),
  INDEX idx_status (status)
);

-- 插入测试数据
INSERT INTO orders VALUES (1, 1, 1);
INSERT INTO orders VALUES (2, 2, 2);

-- 场景1:NOT IN
EXPLAIN SELECT * FROM orders WHERE user_id NOT IN (1, 2);
-- 结果:type=ALL,全表扫描,索引失效

-- 场景2:NOT EXISTS
EXPLAIN SELECT * FROM orders o WHERE NOT EXISTS (SELECT 1 FROM users u WHERE u.id = o.user_id AND u.status = 1);
-- 结果:type=ALL,全表扫描,索引失效

-- 场景3:NOT IN + 子查询
EXPLAIN SELECT * FROM orders WHERE user_id NOT IN (SELECT id FROM users WHERE status = 1);
-- 结果:type=ALL,全表扫描,索引失效

原因分析

  • NOT IN和NOT EXISTS的执行效率通常较低
  • MySQL优化器认为全表扫描比索引扫描更高效
  • 子查询的结果集大小会影响索引使用

优化建议

  • 使用LEFT JOIN替代NOT IN / NOT EXISTS:SELECT o.* FROM orders o LEFT JOIN users u ON o.user_id = u.id WHERE u.id IS NULL;
  • 考虑使用其他条件替代NOT IN,例如NOT NULL、<>等
  • 确保子查询的结果集较小

NULL值处理

场景描述:使用IS NULL或IS NOT NULL时,可能导致索引失效。

示例

sql
-- 表结构
CREATE TABLE users (
  id INT PRIMARY KEY,
  name VARCHAR(50),
  email VARCHAR(50),
  phone VARCHAR(20),
  INDEX idx_email (email)
);

-- 插入测试数据
INSERT INTO users VALUES (1, 'user1', 'user1@example.com', '13800138001');
INSERT INTO users VALUES (2, 'user2', NULL, '13800138002');

-- 场景1:IS NULL
EXPLAIN SELECT * FROM users WHERE email IS NULL;
-- 结果:type=ref,索引扫描,索引有效

-- 场景2:IS NOT NULL
EXPLAIN SELECT * FROM users WHERE email IS NOT NULL;
-- 结果:type=ALL,全表扫描,索引失效

-- 场景3:NOT NULL约束
ALTER TABLE users MODIFY email VARCHAR(50) NOT NULL;
EXPLAIN SELECT * FROM users WHERE email IS NOT NULL;
-- 结果:type=index,索引扫描,索引有效

原因分析

  • IS NULL通常可以使用索引,因为NULL值在索引中是单独存储的
  • IS NOT NULL是否使用索引取决于NULL值的比例,如果NULL值很少,可能使用索引;如果NULL值很多,可能使用全表扫描
  • 当字段有NOT NULL约束时,IS NOT NULL可以使用索引

优化建议

  • 为字段添加NOT NULL约束(如果业务允许)
  • 使用默认值替代NULL值
  • 考虑将NULL值转换为特定的默认值

联合索引顺序问题

场景描述:联合索引的顺序不当,导致索引失效。

示例

sql
-- 表结构
CREATE TABLE orders (
  id INT PRIMARY KEY,
  user_id INT,
  order_date DATETIME,
  amount DECIMAL(10,2),
  INDEX idx_order_date_user_id (order_date, user_id)
);

-- 插入测试数据
INSERT INTO orders VALUES (1, 1, '2023-01-01', 100.00);
INSERT INTO orders VALUES (2, 2, '2023-01-02', 200.00);

-- 场景1:按照联合索引顺序查询
EXPLAIN SELECT * FROM orders WHERE order_date = '2023-01-01' AND user_id = 1;
-- 结果:type=ref,索引扫描,索引有效

-- 场景2:跳过联合索引的第一个字段
EXPLAIN SELECT * FROM orders WHERE user_id = 1;
-- 结果:type=ALL,全表扫描,索引失效

-- 场景3:联合索引顺序与查询条件顺序不一致
EXPLAIN SELECT * FROM orders WHERE user_id = 1 AND order_date = '2023-01-01';
-- 结果:type=ref,索引扫描,索引有效(MySQL会自动排序)

原因分析

  • 联合索引的生效顺序是从左到右
  • 跳过联合索引的第一个字段,无法使用索引
  • 查询条件顺序与联合索引顺序不一致时,MySQL会自动排序,不影响索引使用

优化建议

  • 按照"最左前缀原则"创建联合索引
  • 将查询频率最高的字段放在联合索引的最左侧
  • 将选择性高的字段放在联合索引的左侧
  • 为单独查询的字段创建单独的索引

全表扫描更高效

场景描述:当查询数据量超过表数据的20%-30%时,MySQL优化器认为全表扫描更高效。

示例

sql
-- 表结构
CREATE TABLE users (
  id INT PRIMARY KEY,
  status INT,
  INDEX idx_status (status)
);

-- 插入测试数据(1000行,其中status=1的有800行)
INSERT INTO users SELECT id, 1 FROM (SELECT 1 AS id UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t1 CROSS JOIN (SELECT 1 AS id UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t2 CROSS JOIN (SELECT 1 AS id UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t3 CROSS JOIN (SELECT 1 AS id UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t4;
INSERT INTO users SELECT id, 2 FROM (SELECT 2001 AS id UNION SELECT 2002 UNION SELECT 2003 UNION SELECT 2004 UNION SELECT 2005) t1 CROSS JOIN (SELECT 1 AS id UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t2;

-- 场景:查询大量数据
EXPLAIN SELECT * FROM users WHERE status = 1;
-- 结果:type=ALL,全表扫描,索引失效

-- 场景:查询少量数据
EXPLAIN SELECT * FROM users WHERE status = 2;
-- 结果:type=ref,索引扫描,索引有效

原因分析

  • 当查询数据量较大时,索引扫描需要多次回表操作,成本较高
  • 全表扫描只需要顺序读取数据页,成本可能更低
  • MySQL优化器会根据索引统计信息和查询条件,计算不同执行计划的成本,选择成本最低的执行计划

优化建议

  • 优化查询条件,减少返回的数据量
  • 使用覆盖索引,避免回表操作:CREATE INDEX idx_status_name ON users(status, name);
  • 考虑分区表,将数据分散到多个分区
  • 调整MySQL优化器参数,例如optimizer_switch='index_merge=on'

其他索引失效场景

场景1:使用!=或<>

sql
EXPLAIN SELECT * FROM users WHERE status != 1;
-- 结果:type=ALL,全表扫描,索引失效

场景2:使用COUNT(*)

sql
EXPLAIN SELECT COUNT(*) FROM users WHERE status = 1;
-- 结果:type=index,索引扫描,索引有效(覆盖索引)

EXPLAIN SELECT COUNT(id) FROM users WHERE status = 1;
-- 结果:type=ref,索引扫描,索引有效

场景3:使用DISTINCT

sql
EXPLAIN SELECT DISTINCT status FROM users;
-- 结果:type=index,索引扫描,索引有效

EXPLAIN SELECT DISTINCT status, name FROM users;
-- 结果:type=ALL,全表扫描,索引失效(如果没有联合索引)

场景4:使用ORDER BY

sql
-- 表结构
CREATE TABLE users (
  id INT PRIMARY KEY,
  name VARCHAR(50),
  age INT,
  INDEX idx_name (name)
);

-- 场景1:ORDER BY + 索引字段
EXPLAIN SELECT * FROM users ORDER BY name;
-- 结果:type=index,索引扫描,索引有效

-- 场景2:ORDER BY + 非索引字段
EXPLAIN SELECT * FROM users ORDER BY age;
-- 结果:type=ALL,全表扫描,使用filesort

-- 场景3:ORDER BY + 索引字段 + 范围查询
EXPLAIN SELECT * FROM users WHERE name > 'user500' ORDER BY name;
-- 结果:type=range,索引扫描,索引有效

-- 场景4:ORDER BY + 多个字段,其中部分字段没有索引
EXPLAIN SELECT * FROM users ORDER BY name, age;
-- 结果:type=index,索引扫描,但需要使用filesort对age排序

如何避免索引失效

索引设计原则

  1. 遵循最左前缀原则:联合索引的顺序应该按照查询频率和选择性排序
  2. 选择高选择性的字段:选择性 = 不同值的数量 / 总行数,选择性越高,索引效果越好
  3. 避免冗余索引:例如已经有了(a, b)索引,就不需要单独的a索引
  4. 使用覆盖索引:包含查询所需的所有字段,避免回表操作
  5. 为常用查询创建索引:分析慢查询日志,为频繁出现的查询创建索引

查询优化建议

  1. 确保字段类型匹配:查询条件中的字段类型与表中定义的类型一致
  2. 避免在索引字段上使用函数或表达式:将函数或表达式移到查询条件的右侧
  3. 使用前缀模糊查询:避免使用后缀或中缀模糊查询
  4. 为OR条件中的所有字段创建索引:或使用UNION替代OR
  5. 将范围查询字段放在联合索引的最后:避免后续索引失效
  6. 使用IN替代ORWHERE id IN (1, 2, 3)WHERE id = 1 OR id = 2 OR id = 3 更高效
  7. 使用LEFT JOIN替代NOT IN / NOT EXISTS:提高查询效率
  8. 为NULL值较多的字段创建索引:或使用默认值替代NULL

数据库参数调整

  1. 调整优化器参数

    • optimizer_switch:启用或禁用某些优化器特性
    • eq_range_index_dive_limit:控制范围查询时是否使用索引潜水(index dive)
    • max_seeks_for_key:控制索引扫描的最大seek次数
  2. 更新索引统计信息

    • ANALYZE TABLE table_name:更新表的统计信息
    • OPTIMIZE TABLE table_name:优化表,重新生成索引统计信息

如何诊断索引失效

使用EXPLAIN分析执行计划

sql
-- 查看执行计划
EXPLAIN SELECT * FROM users WHERE name = 'test';

-- 查看详细执行计划
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE name = 'test';

-- 查看执行计划的警告信息
EXPLAIN WARNINGS SELECT * FROM users WHERE name = 'test';

使用SHOW STATUS查看索引使用情况

sql
-- 查看索引使用情况
SHOW STATUS LIKE 'Handler_read%';
-- Handler_read_first:索引第一个条目的读取次数
-- Handler_read_key:索引条目的读取次数
-- Handler_read_next:索引下一个条目的读取次数
-- Handler_read_prev:索引前一个条目的读取次数
-- Handler_read_rnd:随机读取次数
-- Handler_read_rnd_next:顺序读取次数

-- 查看全表扫描次数
SHOW STATUS LIKE 'Select_scan';

-- 查看索引扫描次数
SHOW STATUS LIKE 'Select_range';

分析慢查询日志

sql
-- 启用慢查询日志
SET global slow_query_log = ON;
SET global slow_query_log_file = '/var/lib/mysql/slow.log';
SET global long_query_time = 1;

-- 分析慢查询日志
mysqldumpslow -s t /var/lib/mysql/slow.log
pt-query-digest /var/lib/mysql/slow.log

使用Performance Schema

sql
-- 启用Performance Schema
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE '%statement%';
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE '%statement%';

-- 查看语句执行情况
SELECT * FROM performance_schema.events_statements_summary_by_digest ORDER BY sum_timer_wait DESC LIMIT 10;

索引失效最佳实践

开发阶段最佳实践

  1. 建立索引设计规范:明确索引的创建、修改和删除流程
  2. 使用ORM框架的索引提示:例如Hibernate的@Index、MyBatis的useIndex
  3. 编写查询时考虑索引:避免使用可能导致索引失效的写法
  4. 定期审查代码中的查询:使用工具分析代码中的慢查询
  5. 编写单元测试验证索引使用:确保查询使用了预期的索引

运维阶段最佳实践

  1. 定期分析慢查询日志:找出索引失效的查询
  2. 监控索引使用情况:使用监控工具监控索引的使用频率和效率
  3. 更新索引统计信息:定期执行ANALYZE TABLE
  4. 优化索引结构:根据查询情况调整索引结构
  5. 定期重建索引:对于频繁更新的表,定期重建索引
  6. 使用工具辅助分析:例如Percona Toolkit、MySQL Enterprise Monitor等

常见误区

  1. 索引越多越好:过多的索引会增加写操作的成本,影响插入、更新和删除的性能
  2. 所有查询都需要索引:对于少量数据的表,全表扫描可能更高效
  3. 主键索引是万能的:主键索引只适合基于主键的查询,对于其他查询需要创建相应的索引
  4. 联合索引可以替代多个单列索引:联合索引的顺序很重要,只有当查询条件匹配最左前缀时才能使用
  5. 索引失效是MySQL的bug:索引失效通常是由于查询条件不符合索引设计或优化器的成本计算导致的

案例分析

案例1:字段类型不匹配导致索引失效

问题描述

  • 表结构:CREATE TABLE users (id INT PRIMARY KEY, phone VARCHAR(20), INDEX idx_phone(phone));
  • 查询语句:SELECT * FROM users WHERE phone = 13800138001;
  • 执行计划:type=ALL,全表扫描,索引失效

分析

  • phone字段是VARCHAR类型,但查询条件使用了数字类型
  • MySQL需要将每行的phone字段转换为数字,无法使用索引

解决方案

  • 修改查询语句,使用字符串类型:SELECT * FROM users WHERE phone = '13800138001';
  • 或添加显式类型转换:SELECT * FROM users WHERE phone = CAST(13800138001 AS CHAR);

案例2:函数导致索引失效

问题描述

  • 表结构:CREATE TABLE orders (id INT PRIMARY KEY, create_time DATETIME, INDEX idx_create_time(create_time));
  • 查询语句:SELECT * FROM orders WHERE DATE(create_time) = '2023-01-01';
  • 执行计划:type=ALL,全表扫描,索引失效

分析

  • 在create_time字段上使用了DATE函数
  • MySQL无法在索引上直接执行DATE函数,需要将每行数据加载到内存中计算

解决方案

  • 修改查询语句,使用范围查询:SELECT * FROM orders WHERE create_time BETWEEN '2023-01-01 00:00:00' AND '2023-01-01 23:59:59';
  • 或创建计算列:ALTER TABLE orders ADD COLUMN create_date DATE GENERATED ALWAYS AS (DATE(create_time)) STORED, ADD INDEX idx_create_date(create_date);

案例3:联合索引顺序导致索引失效

问题描述

  • 表结构:CREATE TABLE orders (id INT PRIMARY KEY, user_id INT, order_date DATETIME, amount DECIMAL(10,2), INDEX idx_order_date_user_id(order_date, user_id));
  • 查询语句:SELECT * FROM orders WHERE user_id = 1 AND order_date > '2023-01-01';
  • 执行计划:type=ALL,全表扫描,索引失效

分析

  • 联合索引的顺序是(order_date, user_id)
  • 查询条件先使用user_id,不符合最左前缀原则
  • MySQL无法使用联合索引

解决方案

  • 调整联合索引的顺序:CREATE INDEX idx_user_id_order_date ON orders(user_id, order_date);
  • 或添加单独的user_id索引:CREATE INDEX idx_user_id ON orders(user_id);

案例4:OR条件导致索引失效

问题描述

  • 表结构:CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(50), phone VARCHAR(20), INDEX idx_name(name));
  • 查询语句:SELECT * FROM users WHERE name = 'user1' OR phone = '13800138001';
  • 执行计划:type=ALL,全表扫描,索引失效

分析

  • OR条件中,name字段有索引,phone字段没有索引
  • MySQL无法使用索引,只能进行全表扫描

解决方案

  • 为phone字段添加索引:CREATE INDEX idx_phone ON users(phone);
  • 或使用UNION替代OR:SELECT * FROM users WHERE name = 'user1' UNION SELECT * FROM users WHERE phone = '13800138001';

总结

索引失效是MySQL性能优化中的常见问题,了解索引失效的常见场景和解决方法,对于DBA优化查询性能至关重要。

在实际运维中,DBA需要:

  1. 了解MySQL索引的工作原理
  2. 掌握常见的索引失效场景
  3. 学会使用工具诊断索引失效问题
  4. 制定合理的索引设计和优化策略
  5. 定期监控和优化索引使用情况

通过不断学习和实践,DBA可以更好地优化MySQL查询性能,提高数据库的可用性和可靠性。