MySQL索引与优化:从原理到实践

一、索引类型详解

1. B-Tree索引(最常用)

B-Tree(平衡树)索引是MySQL中最常见的索引类型,InnoDB引擎实际使用的是B+Tree结构。

图1

特点:

  • 所有叶子节点在同一层
  • 非叶子节点存储键值和指针
  • 叶子节点包含所有索引列数据和指向数据的指针

适用场景:

  • 全值匹配(=)
  • 范围查询(>, <, BETWEEN)
  • 前缀匹配(LIKE 'abc%')

实践建议

-- 创建B-Tree索引
CREATE INDEX idx_name ON users(last_name, first_name);

-- 适合的查询
SELECT * FROM users WHERE last_name = 'Smith' AND first_name = 'John';
SELECT * FROM users WHERE last_name BETWEEN 'A' AND 'M';

2. 哈希索引

基于哈希表实现,只有Memory引擎显式支持,InnoDB有自适应哈希索引功能。

特点:

  • 精确匹配极快(O(1)复杂度)
  • 不支持范围查询
  • 不支持排序
  • 存在哈希冲突

实践建议

-- Memory引擎创建哈希索引
CREATE TABLE hash_table (
    id INT,
    name VARCHAR(100),
    INDEX USING HASH (id)
) ENGINE=MEMORY;

3. 全文索引

用于文本内容的搜索,支持自然语言和布尔搜索模式。

-- 创建全文索引
CREATE FULLTEXT INDEX ft_idx ON articles(content);

-- 使用全文搜索
SELECT * FROM articles 
WHERE MATCH(content) AGAINST('数据库' IN NATURAL LANGUAGE MODE);

实践建议

  • 适用于大文本字段搜索
  • 5.6+版本InnoDB支持全文索引
  • 注意最小词长配置(ft_min_word_len)

4. 空间索引(R-Tree)

用于地理空间数据,遵循OpenGIS标准。

CREATE TABLE spatial_table (
    id INT PRIMARY KEY,
    location GEOMETRY NOT NULL,
    SPATIAL INDEX(location)
);

-- 空间查询
SELECT * FROM spatial_table 
WHERE ST_Contains(location, POINT(10,20));

5. 组合索引(最左前缀原则)

多个列组合创建的索引,遵循最左前缀匹配原则。

CREATE INDEX idx_name_age ON employees(last_name, first_name, age);

匹配情况分析

  • WHERE last_name='Smith'
  • WHERE last_name='Smith' AND first_name='John'
  • WHERE last_name='Smith' AND first_name='John' AND age=30
  • WHERE first_name='John' (不匹配最左前缀)
  • WHERE age=30 (不匹配最左前缀)

二、索引优化策略

1. EXPLAIN执行计划分析

EXPLAIN SELECT * FROM users WHERE last_name = 'Smith'\G

关键字段解读:

  • type:从最好到最差依次为 system > const > eq_ref > ref > range > index > ALL
  • key:实际使用的索引
  • rows:预估需要检查的行数
  • Extra:额外信息(Using index/Using filesort/Using temporary等)

2. 索引选择性

选择性 = 不重复的索引值数量 / 表记录总数

高选择性列更适合建索引:

-- 计算选择性
SELECT 
    COUNT(DISTINCT last_name)/COUNT(*) AS selectivity 
FROM users;

实践建议

  • 选择性>0.1的列适合建索引
  • 性别这种低选择性列不适合单独建索引

3. 覆盖索引

索引包含查询所需的所有字段,无需回表。

-- 创建覆盖索引
CREATE INDEX idx_covering ON orders(user_id, order_date, amount);

-- 使用覆盖索引的查询
SELECT user_id, order_date, amount FROM orders 
WHERE user_id = 100 AND order_date > '2023-01-01';

4. 索引下推(ICP,5.6+)

存储引擎层过滤数据,减少回表次数。

-- 假设有索引 (a, b)
SELECT * FROM table WHERE a = 'xxx' AND b LIKE '%yyy%';

优化效果

  • 5.6前:先通过a='xxx'定位记录,再回表检查b条件
  • 5.6+:在索引层同时检查a和b条件,减少回表

三、查询优化实战

1. 慢查询日志分析

配置:

slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 2
log_queries_not_using_indexes = 1

分析工具:

mysqldumpslow -s t /var/log/mysql/mysql-slow.log
pt-query-digest /var/log/mysql/mysql-slow.log

2. 查询缓存(注意:8.0已移除)

-- 查看查询缓存状态
SHOW VARIABLES LIKE 'query_cache%';

-- 查询缓存命中率
SHOW STATUS LIKE 'Qcache%';

实践建议

  • 写频繁的系统建议关闭查询缓存
  • 5.7版本可设置query_cache_type=DEMAND按需使用

3. 优化器提示(HINT)

-- 强制使用某个索引
SELECT * FROM users FORCE INDEX(idx_last_name) WHERE last_name = 'Smith';

-- 忽略索引
SELECT * FROM users IGNORE INDEX(idx_age) WHERE age > 30;

-- 关闭ICP
SELECT * FROM users WHERE last_name = 'Smith' AND first_name LIKE 'J%' 
OPTION (MAX_EXECUTION_TIME=1000, NO_ICP);

四、综合优化案例

问题查询

SELECT u.user_id, u.user_name, o.order_count
FROM users u
JOIN orders o ON u.user_id = o.user_id
WHERE u.register_time > '2023-01-01'
ORDER BY o.order_count DESC
LIMIT 100;

优化步骤

  1. 检查索引:

    -- 确保users表有register_time索引
    CREATE INDEX idx_register ON users(register_time);
    
    -- 确保orders表有user_id和order_count的复合索引
    CREATE INDEX idx_user_orders ON orders(user_id, order_count);
  2. 改写查询:

    SELECT u.user_id, u.user_name, o.order_count
    FROM (
     SELECT user_id 
     FROM users 
     WHERE register_time > '2023-01-01'
     LIMIT 100
    ) AS u_ids
    JOIN users u ON u_ids.user_id = u.user_id
    JOIN orders o ON u.user_id = o.user_id
    ORDER BY o.order_count DESC
    LIMIT 100;
  3. 使用STRAIGHT_JOIN优化连接顺序:

    SELECT STRAIGHT_JOIN u.user_id, u.user_name, o.order_count
    FROM users u
    JOIN orders o ON u.user_id = o.user_id
    WHERE u.register_time > '2023-01-01'
    ORDER BY o.order_count DESC
    LIMIT 100;

五、最佳实践总结

  1. 索引设计原则

    • 为WHERE、JOIN、ORDER BY子句中的列建索引
    • 遵循最左前缀原则设计组合索引
    • 避免过度索引,每个索引都有维护成本
  2. 查询编写建议

    • 避免SELECT *,只查询需要的列
    • 小心使用LIKE '%前缀'模糊查询
    • 注意IN和OR可能导致索引失效
    • 避免在索引列上使用函数
  3. 监控与调优

    • 定期分析慢查询日志
    • 使用Performance Schema监控性能
    • 关注索引统计信息的准确性

通过合理使用索引和优化查询,可以显著提升MySQL数据库性能。建议在实际应用中结合EXPLAIN分析工具,持续监控和优化数据库性能。

添加新评论