索引夺命10连问,你能顶住第几问?

前言

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

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

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

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

希望对你会有所帮助。

最近准备面试的小伙伴,可以看一下这个宝藏网站(Java突击队):www.susan.net.cn,里面:面试八股文、场景设计题、面试真题、7个项目实战、工作内推什么都有

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

1.1 索引的本质

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

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

sql 复制代码
-- 没有索引的查询(全表扫描)
SELECT * FROM users WHERE name = '苏三'; -- 需要遍历所有记录

-- 有索引的查询(索引扫描)
CREATE INDEX idx_name ON users(name);
SELECT * FROM users WHERE name = '苏三'; -- 通过索引快速定位

1.2 索引的工作原理

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

二、索引的10个常见问题

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

场景还原

sql 复制代码
CREATE INDEX idx_name ON users(name);
SELECT * FROM users WHERE name LIKE '%苏三%'; -- 还是很慢!

原因分析

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

解决方案

sql 复制代码
-- 方案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 ON users(name);
SELECT * FROM users WHERE MATCH(name) AGAINST('苏三');

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

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

sql 复制代码
-- 每个索引都会影响写性能
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.联合索引的最左前缀原则是什么?

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

sql 复制代码
-- 创建联合索引
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; -- × 不符合最左前缀

联合索引结构

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

选择原则

  1. 高选择性字段在前:选择性高的字段能更快过滤数据
  2. 经常查询的字段在前:优先满足常用查询场景
  3. 等值查询在前,范围查询在后
sql 复制代码
-- 计算字段选择性
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.什么是覆盖索引?为什么重要?

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

sql 复制代码
-- 不是覆盖索引(需要回表)
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值的问题

sql 复制代码
-- 创建索引
CREATE INDEX idx_email ON users(email);

-- 查询NULL值
SELECT * FROM users WHERE email IS NULL; -- 可能不使用索引
SELECT * FROM users WHERE email IS NOT NULL; -- 可能不使用索引

NULL值可能不使用索引。

解决方案

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

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

索引优化排序和分组

sql 复制代码
-- 创建索引
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; -- × 排序方向不一致

最近为了帮助大家找工作,专门建了一些工作内推群,各大城市都有,欢迎各位HR和找工作的小伙伴进群交流,群里目前已经收集了不少的工作内推岗位。加苏三的微信:li_su223,备注:掘金+所在城市,即可进群。

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

常见索引失效场景

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

使用EXPLAIN分析

sql 复制代码
EXPLAIN SELECT * FROM users WHERE name = '苏三';

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

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

定期索引维护

sql 复制代码
-- 查看索引使用情况(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;

索引监控

sql 复制代码
-- 开启索引监控(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示例

sql 复制代码
-- 创建包含索引(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 索引设计检查清单

四、实战案例:电商系统索引设计

4.1 用户表索引设计

sql 复制代码
-- 用户表结构
CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL,
    phone VARCHAR(20),
    age INT,
    city VARCHAR(50),
    created_at TIMESTAMP,
    is_active BOOLEAN
);

-- 推荐索引
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_city_age ON users(city, age);
CREATE INDEX idx_users_created ON users(created_at) WHERE is_active = true;

4.2 订单表索引设计

sql 复制代码
-- 订单表结构
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT,
    status VARCHAR(20),
    amount DECIMAL(10,2),
    created_at TIMESTAMP,
    updated_at TIMESTAMP
);

-- 推荐索引
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status_created ON orders(status, created_at);
CREATE INDEX idx_orders_created_amount ON orders(created_at, amount);

总结

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

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

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

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。

相关推荐
嘉伟桑1 小时前
Python调用电价API返回JSON示例:分时电价、现货电价和字段解析
后端
武子康1 小时前
Shippy:确定性工具、会话级 Sandbox 与 Live-Data Eval(4 类收敛 + 7 步实现方案 + 6 类评测指标)
前端·人工智能·后端
我叫黑大帅1 小时前
我为什么单一消费者的场景下,要用 Redis List 当消息队列?
redis·后端·面试
AskHarries2 小时前
文件上传系统
后端
止语Lab3 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活3 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困3 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn3 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530143 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908403 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端