索引在查询优化中的作用
索引(Index)是数据库优化中最重要的机制之一,它的作用主要包括:
-
加速数据查找
-
索引类似于书的目录,能够快速定位所需数据,而不需要扫描整个表。
-
示例:如果
employees
表id
列有索引:SELECT * FROM employees WHERE id = 123;
-
无索引:全表扫描(O(N))。
-
有索引:二分查找或 B+ 树搜索(O(log N))。
-
-
-
优化排序(ORDER BY)
-
索引列上的排序可以直接使用索引,加速查询:
SELECT * FROM employees ORDER BY salary;
-
有索引(salary 列):数据库可以直接按索引顺序返回数据,而不需要额外排序。
-
无索引:数据库需要排序整个结果集(O(N log N))。
-
-
-
优化分组(GROUP BY)
-
索引列上的 GROUP BY 计算更快:
SELECT department_id, COUNT(*) FROM employees GROUP BY department_id;
-
有索引(department_id):数据已按
department_id
排序,数据库可以直接进行分组。 -
无索引:数据库可能需要全表扫描和排序。
-
-
-
提高关联查询效率(JOIN)
-
索引能加速表之间的关联:
SELECT e.name, d.name FROM employees e JOIN departments d ON e.department_id = d.id;
-
有索引(e.department_id, d.id):数据库可以使用索引快速匹配两张表。
-
无索引:数据库可能需要嵌套循环(Nested Loop Join),大数据量时性能会很差。
-
-
索引失效的常见情况
尽管索引能优化查询,但某些情况下索引会失效,导致数据库退化为全表扫描,常见原因包括:
1. 使用 OR
连接多个查询条件(单列索引失效)
SELECT * FROM employees WHERE department_id = 3 OR salary > 5000;
索引失效原因:
-
如果
department_id
和salary
各自有单独的索引,数据库通常不会使用索引,而是进行全表扫描。
✅ 优化方案
-
使用联合索引(
department_id, salary
):CREATE INDEX idx_dept_salary ON employees(department_id, salary);
-
拆分查询再
UNION ALL
:SELECT * FROM employees WHERE department_id = 3 UNION ALL SELECT * FROM employees WHERE salary > 5000;
这样可以让
department_id
和salary
各自利用索引。
2. 使用 LIKE '%关键字%'
(前导模糊查询失效)
SELECT * FROM employees WHERE name LIKE '%John%';
索引失效原因:
-
%
号出现在开头,数据库无法使用 B+ 树索引,必须全表扫描。
✅ 优化方案
-
避免前导
%
:SELECT * FROM employees WHERE name LIKE 'John%';
-
这样索引可以高效查找匹配的记录。
-
-
使用全文索引(Full-Text Index)(适用于 MySQL):
ALTER TABLE employees ADD FULLTEXT(name); SELECT * FROM employees WHERE MATCH(name) AGAINST('John');
3. 计算、函数或类型转换导致索引失效
SELECT * FROM employees WHERE YEAR(birth_date) = 1990;
索引失效原因:
-
YEAR(birth_date)
对索引列做了计算,导致数据库无法直接使用索引。 -
需要对每一行数据计算
YEAR(birth_date)
,然后进行匹配,导致全表扫描。
✅ 优化方案
-
避免对索引列进行计算,改写 SQL:
SELECT * FROM employees WHERE birth_date BETWEEN '1990-01-01' AND '1990-12-31';
-
这样
birth_date
索引能直接生效。
-
4. !=
、<>
、NOT IN
可能导致索引失效
SELECT * FROM employees WHERE department_id != 3;
索引失效原因:
-
!=
会导致数据库遍历所有记录来查找不等于3
的值,索引无法高效筛选数据。
✅ 优化方案
-
用
BETWEEN
或IN
代替:SELECT * FROM employees WHERE department_id IN (1, 2, 4, 5);
-
这样索引可以生效。
-
5. IS NULL
或 IS NOT NULL
可能导致索引失效
SELECT * FROM employees WHERE department_id IS NULL;
索引失效原因:
-
索引通常不会存储 NULL 值,导致数据库可能执行全表扫描。
✅ 优化方案
-
使用默认值代替 NULL(如
0
):ALTER TABLE employees MODIFY department_id INT DEFAULT 0; UPDATE employees SET department_id = 0 WHERE department_id IS NULL;
-
建立索引时包含 NULL 值:
CREATE INDEX idx_department_id ON employees(department_id) WHERE department_id IS NOT NULL;
6. 隐式类型转换导致索引失效
SELECT * FROM employees WHERE phone_number = 1234567890;
索引失效原因:
-
phone_number
是 VARCHAR,但查询值是 INT,数据库会进行类型转换:SELECT * FROM employees WHERE CAST(phone_number AS INT) = 1234567890;
-
索引无法生效,导致全表扫描。
✅ 优化方案
-
保持数据类型一致:
SELECT * FROM employees WHERE phone_number = '1234567890';
总结
索引失效原因 | 优化方案 |
---|---|
OR 条件 | 使用 联合索引 或 UNION ALL |
LIKE '%关键字%' | 避免 % 前导,使用 全文索引 |
计算、函数 (YEAR(birth_date) ) | 重写查询,避免索引列计算 |
!= 、<> 、NOT IN | 用 BETWEEN 或 IN 代替 |
IS NULL 或 IS NOT NULL | 使用默认值替代 NULL 或 索引 NULL 值 |
隐式类型转换 | 确保数据类型匹配 |
✅ 最佳实践
-
创建合适的索引(主键、联合索引、覆盖索引)。
-
避免索引失效的查询模式(计算、NULL、
LIKE '%...'
)。 -
定期分析
EXPLAIN
查询执行计划,找出未使用索引的 SQL 并优化。