索引夺命 10 连问

MySQL关系型数据库NoSQL数据库

大家好,我是苏三,又跟大家见面了。

前言

今天我们来聊聊让无数开发者又爱又恨的——数据库索引

相信不少小伙伴在工作中都遇到过这样的场景:

  • 明明已经加了索引,为什么查询还是慢?
  • 为什么有时候索引反而导致性能下降?
  • 联合索引到底该怎么设计才合理?

别急,今天我就通过10个问题,带你彻底搞懂索引的奥秘!

希望对你会有所帮助。

一、什么是索引?为什么需要索引?

1.1 索引的本质

简单来说,索引就是数据的目录

就像一本书的目录能帮你快速找到内容一样,数据库索引能帮你快速定位数据。

  
-- 没有索引的查询(全表扫描)  
SELECT * FROM users WHERE name = '苏三'; -- 需要遍历所有记录  
  
-- 有索引的查询(索引扫描)  
CREATE INDEX idx\_name ON users(name);  
SELECT * FROM users WHERE name = '苏三'; -- 通过索引快速定位  

1.2 索引的工作原理

picture.image

索引的底层结构(B+树)picture.image

二、索引的10个常见问题

1.为什么我加了索引,查询还是慢?

场景还原

  
CREATE INDEX idx\_name ON users(name);  
SELECT * FROM users WHERE name LIKE '%苏三%'; -- 还是很慢!  

原因分析

  1. 前导通配符LIKE '%苏三% 导致索引失效
  2. 索引选择性差 :如果name字段大量重复,索引效果不佳
  3. 回表代价高 :索引覆盖不全,需要回表查询

解决方案

  
-- 方案1:避免前导通配符  
SELECT * FROM users WHERE name LIKE'苏三%';  
  
-- 方案2:使用覆盖索引  
CREATE INDEX idx\_name\_covering ON users(name, id, email);  
SELECT name, id, email FROM users WHERE name LIKE'苏三%'; -- 不需要回表  
  
-- 方案3:使用全文索引(对于文本搜索)  
CREATE FULLTEXT INDEX ft\_name ONusers(name);  
SELECT * FROM users WHERE MATCH(name) AGAINST('苏三');  

2.索引是不是越多越好?

绝对不是! 索引需要维护代价:

  
-- 每个索引都会影响写性能  
INSERT INTO users (name, email, age) VALUES ('苏三', 'susan@example.com', 30);  
-- 需要更新:   
-- 1. 主键索引  
-- 2. idx\_name索引(如果存在)  
-- 3. idx\_email索引(如果存在)  
-- 4. idx\_age索引(如果存在)  

索引的代价

  1. 存储空间 :每个索引都需要额外的磁盘空间
  2. 写操作变慢 :INSERT/UPDATE/DELETE需要维护所有索引
  3. 优化器负担 :索引太多会增加查询优化器的选择难度

黄金法则 :一般建议表的索引数量不超过5-7个

3.联合索引的最左前缀原则是什么?

最左前缀原则 :联合索引只能从最左边的列开始使用

  
-- 创建联合索引  
CREATE INDEX idx\_name\_age ON users(name, age);  
  
-- 能使用索引的查询  
SELECT * FROM users WHERE name = '苏三'; -- √ 使用索引  
SELECT * FROM users WHERE name = '苏三' AND age = 30; -- √ 使用索引  
SELECT * FROM users WHERE age = 30 AND name = '苏三'; -- √ 优化器会调整顺序  
  
-- 不能使用索引的查询  
SELECT * FROM users WHERE age = 30; -- × 不符合最左前缀  

联合索引结构

picture.image

4.如何选择索引字段的顺序?

选择原则

  1. 高选择性字段在前 :选择性高的字段能更快过滤数据
  2. 经常查询的字段在前 :优先满足常用查询场景
  3. 等值查询在前,范围查询在后
  
-- 计算字段选择性  
SELECT   
    COUNT(DISTINCT name) / COUNT(*) as name\_selectivity,  
    COUNT(DISTINCT age) / COUNT(*) as age\_selectivity,  
    COUNT(DISTINCT city) / COUNT(*) as city\_selectivity  
FROM users;  
  
-- 根据选择性决定索引顺序  
CREATE INDEX idx\_name\_city\_age ON users(name, city, age); -- name选择性最高  

5.什么是覆盖索引?为什么重要?

覆盖索引 :索引包含了查询需要的所有字段,不需要回表查询

  
-- 不是覆盖索引(需要回表)  
CREATE INDEX idx\_name ON users(name);  
SELECT * FROM users WHERE name = '苏三'; -- 需要回表查询其他字段  
  
-- 覆盖索引(不需要回表)  
CREATE INDEX idx\_name\_covering ON users(name, email, age);  
SELECT name, email, age FROM users WHERE name = '苏三'; -- 所有字段都在索引中  

覆盖索引的优势

  1. 避免回表 :减少磁盘IO
  2. 减少内存占用 :只需要读取索引页
  3. 提升性能 :查询速度更快

6.NULL值对索引有什么影响?

NULL值的问题

  
-- 创建索引  
CREATE INDEX idx\_email ON users(email);  
  
-- 查询NULL值  
SELECT * FROM users WHERE email IS NULL; -- 可能不使用索引  
SELECT * FROM users WHERE email IS NOT NULL; -- 可能不使用索引  

解决方案

  1. 避免NULL值 :设置默认值
  2. 使用函数索引 (MySQL 8.0+)
  
-- 使用函数索引处理NULL值  
CREATE INDEX idx\_email\_null ON users((COALESCE(email, '')));  
SELECT * FROM users WHERE COALESCE(email, '') = '';  

7.索引对排序和分组有什么影响?

索引优化排序和分组

  
-- 创建索引  
CREATE INDEX idx\_age\_name ON users(age, name);  
  
-- 索引优化排序  
SELECT * FROM users ORDER BY age, name; -- √ 使用索引避免排序  
  
-- 索引优化分组  
SELECT age, COUNT(*) FROM users GROUP BY age; -- √ 使用索引优化分组  
  
-- 无法使用索引排序的情况  
SELECT * FROM users ORDER BY name, age; -- × 不符合最左前缀  
SELECT * FROM users ORDER BY age DESC, name ASC; -- × 排序方向不一致  

8.如何发现索引失效的场景?

常见索引失效场景

  1. 函数操作WHERE YEAR(create\_time) = 2023
  2. 类型转换WHERE phone = 13800138000 (phone是varchar)
  3. 数学运算WHERE age + 1 > 30
  4. 前导通配符WHERE name LIKE '%苏三'

使用EXPLAIN分析

  
EXPLAIN SELECT * FROM users WHERE name = '苏三';  
  
-- 查看关键指标:  
-- type: const|ref|range|index|ALL(性能从好到坏)  
-- key: 实际使用的索引  
-- rows: 预估扫描行数  
-- Extra: Using index(覆盖索引)| Using filesort(需要排序)| Using temporary(需要临时表)  

9.如何维护和优化索引?

定期索引维护

  
-- 查看索引使用情况(MySQL)  
SELECT * FROM sys.schema\_index\_statistics   
WHERE table\_schema = 'your\_database'AND table\_name = 'users';  
  
-- 重建索引(优化索引碎片)  
ALTER TABLE users REBUILD INDEX idx\_name;  
  
-- 分析索引使用情况  
ANALYZE TABLE users;  

索引监控

  
-- 开启索引监控(Oracle)  
ALTER INDEX idx\_name MONITORING USAGE;  
  
-- 查看索引使用情况  
SELECT * FROM v$object\_usage WHERE index\_name = 'IDX\_NAME';  

10.不同数据库的索引有什么差异?

MySQL vs PostgreSQL索引差异

| 特性 | MySQL | PostgreSQL | | --- | --- | --- | | 索引类型 | B+Tree, Hash, Fulltext | B+Tree, Hash, GiST, SP-GiST | | 覆盖索引 | 支持 | 支持(使用INCLUDE) | | 函数索引 | 8.0+支持 | 支持 | | 部分索引 | 支持 | 支持 | | 索引组织表 | 聚簇索引 | 堆表 |

PostgreSQL示例

  
-- 创建包含索引(Covering Index)  
CREATE INDEX idx\_users\_covering ON users (name) INCLUDE (email, age);  
  
-- 创建部分索引(Partial Index)  
CREATE INDEX idx\_active\_users ON users (name) WHERE is\_active = true;  
  
-- 创建表达式索引(Expression Index)  
CREATE INDEX idx\_name\_lower ON users (LOWER(name));  

三、索引设计最佳实践

3.1 索引设计原则

  1. 按需创建 :只为经常查询的字段创建索引
  2. 选择合适类型 :根据场景选择B-Tree、Hash、全文索引等
  3. 考虑复合索引 :使用复合索引减少索引数量
  4. 避免过度索引 :每个索引都有维护成本
  5. 定期维护 :重建索引,优化索引碎片

3.2 索引设计检查清单

picture.image

总结

  1. 理解原理 :掌握B+树索引的工作原理和特性。
  2. 合理设计 :遵循最左前缀原则,选择合适的索引顺序。
  3. 避免失效 :注意索引失效的常见场景。
  4. 覆盖索引 :尽可能使用覆盖索引减少回表。
  5. 定期维护 :监控索引使用情况,定期优化重建。
  6. 权衡利弊 :索引不是越多越好,要权衡查询性能和写成本。

好的索引设计是数据库性能的基石。

不要盲目添加索引,要基于实际查询需求和数据分布来科学设计。

最后欢迎加入苏三的星球,你将获得:100万QPS短链系统、复杂的商城微服务系统、苏三AI项目、刷题吧小程序、秒杀系统、商城系统、秒杀系统、代码生成工具等8个项目的源代码、开发教程和技术答疑。

系统设计、性能优化、技术选型、底层原理、Spring源码解读、工作经验分享、痛点问题、面试八股文等多个优质专栏。

还有1V1免费修改简历、技术答疑、职业规划、送书活动、技术交流。

扫描下方二维码,可以优惠30元:

picture.image

只有20张优惠券, 数量有限,先到先得。

目前星球已经更新了5800+篇优质内容,还在持续爆肝中.....

星球已经被官方推荐了3次,收到了小伙伴们的一致好评。戳我加入学习,已有1900+小伙伴加入学习。

最后推荐一下我的技术专栏《性能优化35讲》,里面包含了:接口调用、Java、JVM、并发编程、MySQL、Redis、ElasticSearch、Spring、SpringBoot等多个性能优化技巧。无论在工作,还是在面试中,都会经常遇到,非常有参考价值。

picture.image

picture.image

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
字节跳动 NoSQL 的实践与探索
随着 NoSQL 的蓬勃发展越来越多的数据存储在了 NoSQL 系统中,并且 NoSQL 和 RDBMS 的界限越来越模糊,各种不同的专用 NoSQL 系统不停涌现,各具特色,形态不一。本次主要分享字节跳动内部和火山引擎 NoSQL 的实践,希望能够给大家一定的启发。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论