今天我们来聊聊让无数开发者又爱又恨的——数据库索引。
相信不少小伙伴在工作中都遇到过这样的场景:
别急,今天我就通过10个问题,带你彻底搞懂索引的奥秘!
希望对你会有所帮助。
简单来说,索引就是数据的目录。
就像一本书的目录能帮你快速找到内容一样,数据库索引能帮你快速定位数据。
-- 没有索引的查询(全表扫描)
SELECT * FROM users WHERE name = '苏三'; -- 需要遍历所有记录
-- 有索引的查询(索引扫描)
CREATE INDEX idx_name ON users(name);
SELECT * FROM users WHERE name = '苏三'; -- 通过索引快速定位
索引的底层结构(B+树):
场景还原:
CREATE INDEX idx_name ON users(name);
SELECT * FROM users WHERE name LIKE '%苏三%'; -- 还是很慢!
原因分析:
LIKE '%苏三%
导致索引失效解决方案:
-- 方案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('苏三');
绝对不是! 索引需要维护代价:
-- 每个索引都会影响写性能
INSERT INTO users (name, email, age) VALUES ('苏三', 'susan@example.com', 30);
-- 需要更新:
-- 1. 主键索引
-- 2. idx_name索引(如果存在)
-- 3. idx_email索引(如果存在)
-- 4. idx_age索引(如果存在)
索引的代价:
黄金法则:一般建议表的索引数量不超过5-7个
最左前缀原则:联合索引只能从最左边的列开始使用
-- 创建联合索引
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; -- × 不符合最左前缀
联合索引结构:
选择原则:
-- 计算字段选择性
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选择性最高
覆盖索引:索引包含了查询需要的所有字段,不需要回表查询
-- 不是覆盖索引(需要回表)
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 = '苏三'; -- 所有字段都在索引中
覆盖索引的优势:
NULL值的问题:
-- 创建索引
CREATE INDEX idx_email ON users(email);
-- 查询NULL值
SELECT * FROM users WHERE email IS NULL; -- 可能不使用索引
SELECT * FROM users WHERE email IS NOT NULL; -- 可能不使用索引
解决方案:
-- 使用函数索引处理NULL值
CREATE INDEX idx_email_null ON users((COALESCE(email, '')));
SELECT * FROM users WHERE COALESCE(email, '') = '';
索引优化排序和分组:
-- 创建索引
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; -- × 排序方向不一致
常见索引失效场景:
WHERE YEAR(create_time) = 2023
WHERE phone = 13800138000
(phone是varchar)WHERE age + 1 > 30
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(需要临时表)
定期索引维护:
-- 查看索引使用情况(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';
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));
好的索引设计是数据库性能的基石。
不要盲目添加索引,要基于实际查询需求和数据分布来科学设计。