数据库索引如何合理创建

wen IT资讯 31

从原理到实战的完整指南

目录导读

  1. 索引的本质与类型 – 理解索引为什么能加速查询
  2. 创建索引的核心原则 – 避免“索引无用”陷阱
  3. 常见场景的索引设计 – 单列、联合、覆盖索引怎么选
  4. 索引维护与性能监测 – 如何发现并修复问题索引
  5. 高频问答 – 解决开发者最常见的索引困惑

索引的本质与类型

Q:索引为什么能提升查询速度?

索引本质上是一种“有序的查找结构”,想象一本没有目录的《百科全书》——你要找“索引”这个词,只能从第1页翻到第1000页,而索引就像书末的关键词目录,直接告诉你“索引”在第312页,数据库索引通过B+树、哈希表等结构,将全表扫描(O(n))降为对数级(O(log n))或常数级(O(1))查找。

数据库索引如何合理创建

常见索引类型:

  • B+树索引(默认InnoDB):支持范围查询、排序、模糊匹配(如like 'ab%'),数据按页存储,非叶子节点仅存键值,叶子节点存完整行或主键。
  • 哈希索引(Memory引擎):等值查询极快(如where id=100),但不支持范围查询和排序。
  • 全文索引:用于MATCH…AGAINST全文搜索,含词根处理、停用词过滤。
  • 空间索引(R树):处理地理坐标数据。

关键认知:索引并非“越多越快”,每增一个索引,写入(INSERT/UPDATE/DELETE)需多维护一份结构,磁盘IO和日志量都会增加。


创建索引的核心原则

Q:如何判断一个字段是否该建索引?

遵循“高区分度、高频查询、低写入负载”三角原则。

场景 建议创建索引 不建议创建索引
字段值几乎唯一(如用户ID、订单号)
常用WHERE条件(如status=‘active’)
JOIN关联字段(如user_id)
性别字段(只有男/女) ×(区分度太低) 全表扫描更快
频繁更新的字段(如last_login_time) 慎用(索引维护成本高) 考虑复合索引覆盖

核心原则一:最左前缀法则

对于联合索引 (a, b, c),查询条件必须从最左列开始。WHERE a=1 AND b=2 能使用索引,但 WHERE b=2 则无法使用,设计时,把区分度高的字段放最左边

核心原则二:避免冗余索引

例如已有 (a, b) 索引,再建 (a) 基本是浪费——前者已覆盖后者,可通过 pt-duplicate-key-checkerinformation_schema 检查。

核心原则三:覆盖索引击败回表

若查询的列全部在索引中(如 SELECT a,b FROM t WHERE a=1,且索引含a,b),则无需回表查询数据行,性能提升显著,常用技巧:将高频查询的字段加入复合索引


常见场景的索引设计

场景1:高并发订单查询

需求:按用户ID和创建时间查最近订单。 错误设计:单独索引 (user_id)(create_time) → 数据库可能只用一个索引,另一个需全表过滤。 正确设计:联合索引 (user_id, create_time),原因:先按用户过滤(高区分度),再按时间排序(利用B+树有序性)。

-- 高效查询(利用索引排序,避免filesort)
SELECT * FROM orders 
WHERE user_id = 123 
ORDER BY create_time DESC 
LIMIT 10;

场景2:模糊搜索(like)

正确用法WHERE name LIKE '张%' 可以使用索引。反例'%张%' 无法使用前缀匹配,只能全表扫描。 优化方案:使用全文索引或Elasticsearch等搜索引擎。

场景3:分页大偏移量

传统 LIMIT 100000, 20 需扫描10万行后丢弃。优化:使用“游标分页”(延迟关联):

-- 先用索引快速定位起始ID
SELECT t.* FROM (SELECT id FROM table WHERE … ORDER BY id LIMIT 100000,20) AS tmp
JOIN table AS t ON t.id = tmp.id;

原理:子查询在索引上完成分页(无需回表),再通过主键关联回原表。

场景4:函数操作导致索引失效

WHERE DATE(create_time) = '2023-01-01' 会使 create_time 索引失效。正确写法

WHERE create_time >= '2023-01-01' AND create_time < '2023-01-02';

同样,WHERE id + 1 = 100 应改为 WHERE id = 99


索引维护与性能监测

Q:如何发现“坏索引”?

  • 慢查询日志slow_query_log)中,若查询耗时久且线程状态为“Sending data”,可能是索引缺失或使用不当。
  • EXPLAIN 分析:关注 type 字段(ALL=全表扫描,ref/range=索引利用良好)、Extra 中的 Using filesort(需在索引设计中解决排序)。
  • sys.schema_unused_indexes:统计从未使用的索引,可考虑删除。

索引维护策略

  • 定期重建碎片ALTER TABLE t ENGINE=InnoDB(重建表,消除碎片)或 OPTIMIZE TABLE(生产环境需谨慎,建议低峰期操作)。
  • 监控索引使用率:通过 performance_schema.table_io_waits_summary_by_index_usage 获取每次查询的IO次数。

高频问答

Q1:主键是自增ID好还是UUID好?

:自增ID更好,B+树插入时,自增ID只需在末尾追加,页分裂少;UUID随机无序,频繁页分裂导致性能下降,若有跨库合并需求,可考虑雪花算法(Snowflake)生成有序全局ID。

Q2:联合索引中,字段顺序是否影响查询?

:极大影响,遵循“等值查询优先,其次范围查询,最后排序”顺序,例:WHERE a=1 AND b>10 ORDER BY c,索引设计为 (a, b, c)a 等值过滤,b 范围过滤,c 利用索引排序。

Q3:为什么给大表加索引会锁表?

:MySQL 5.6+ 支持在线DDL(ALGORITHM=INPLACE),但若表数据量大,仍可能导致主从延迟或IO升高,建议使用 pt-online-schema-change 工具无锁添加索引。

Q4:索引字段为NULL怎么办?

:B+树索引不存储NULL值,WHERE column IS NULL 无法使用索引。建议:用默认值(如空字符串、0)替代NULL,并对“默认值”业务场景合理处理。

Q5:为什么我的查询明明有索引,却走了全表扫描?

常见原因

  • 使用了 OR 条件,且部分字段无索引(需改为 UNION ALL)。
  • 数据量少(MySQL优化器认为全表扫描比索引查找更快)。
  • 隐式类型转换:如 WHERE id = '123'(id为整型)导致索引失效。
  • 查询结果超过表数据20%左右,行数过多时索引可能被放弃(可强制索引 FORCE INDEX)。

合理创建索引并非一劳永逸,建议在开发阶段通过慢查询日志和 EXPLAIN 持续调优,生产环境每隔半年审查一次索引使用情况。索引是数据库优化的起点,但靠索引解决所有问题是认知的终点——当业务成为瓶颈时,分库分表、读写分离、缓存层才是答案。

抱歉,评论功能暂时关闭!