索引绝对是让数据库变快的神器,但俗话说得好,“过犹不及”。不是每个索引都有用,索引太多反而会拖后腿。这听起来有点反直觉,但事实就是这样。咱们来仔细聊聊。
想象一下一个超大的图书馆,里面有好几个目录,比如按作者、按类型、按出版年份。每个目录都能帮你更快找到书。但如果目录太多,比如每个书名的单词、每个细节都建个目录,最后你会发现:查找反而更慢了,目录占了很多空间,管理员还得不停地维护这些列表。
数据库里的索引其实也差不多:它们能帮你快速查数据,但如果索引太多,每次插入或修改数据都得更新一堆索引,操作就变得很重。而且磁盘空间也会被吃掉。更坑的是,索引太多,数据库自己都可能搞不清到底该用哪个索引。
所以,和图书馆的目录一样,索引也不能乱加——有几个真正有用的比一堆没用的强多了。
来玩个“PostgreSQL侦探”小游戏。假设你给同一列加了三个索引,你觉得这样能让性能更好。可想想看:
- 如果你的表是一个超大的学生名单,三个索引,每加一个学生就要更新三次索引。这哪是加速,简直是拖慢了吧?
- 如果你有10个这样的表,每个都被索引压垮?整个数据库性能直接崩掉。
怎么判断你有没有索引太多的问题?
第一步,先看看你现在有多少索引。在PostgreSQL里可以用这个命令:
\d 表名
这个命令会显示表、字段和相关的索引。如果你发现某个表挂了一堆索引,那就要警惕了。
还有个很有用的工具——系统视图pg_stat_user_indexes。它能显示索引的使用频率,帮你找出“吃灰”的索引:
SELECT
relname AS table_name,
indexrelname AS index_name,
idx_scan AS index_scans
FROM
pg_stat_user_indexes
WHERE
idx_scan = 0;
如果idx_scan等于0,说明这个索引一次都没被用过。这种索引就是删掉的最佳候选。
索引太多的例子
假设有个用户表:
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE,
username VARCHAR(50),
created_at TIMESTAMP DEFAULT NOW()
);
我们有三个索引:
-- email的索引
CREATE INDEX idx_users_email ON users (email);
-- username的索引
CREATE INDEX idx_users_username ON users (username);
-- created_at的索引
CREATE INDEX idx_users_created_at ON users (created_at);
现在来看下常见的查询:
- 按email查用户。
- 按username查用户。
- 按
created_at排序用户。
看起来这些索引挺有用。但问题来了:如果这些查询很少发生(比如一周一次),那建索引就没啥意义。更糟的是,有些索引根本没被用过,只会拖慢插入和更新。
举个例子:假设users表里有如下数据:
| user_id | username | created_at | |
|---|---|---|---|
| 1 | alex.lin@mail.com | alexlin | 2024-06-15 10:23:00 |
| 2 | anna.min@mail.com | annamin | 2024-06-16 12:47:00 |
| 3 | otto.song@mail.com | ottosong | 2024-06-17 08:30:00 |
| 4 | maria.chi@mail.com | mariachi | 2024-06-18 14:10:00 |
如果几乎没人按username查,idx_users_username这个索引就一次都没用过(idx_scan = 0),完全可以删掉让性能更好。
所以说,索引是好东西,但要用得聪明。宁可有几个真正有用的,也别搞一堆没用的。
怎么避免索引太多
- 分析索引的使用情况。 刚才说过了,用
pg_stat_user_indexes查查索引的使用频率。如果某个索引几乎没用过,基本可以删掉:
DROP INDEX IF EXISTS 索引名;
- 只给常用查询加索引。 加索引前先问自己几个问题:
- 这个字段经常出现在
WHERE、ORDER BY、GROUP BY里吗? - 表的数据量大吗?
- 不加索引查询真的很慢吗?
只要有一个问题答案是“不是”,那这个索引大概率是多余的。
- 用组合索引。 如果你经常在一个查询里用到多个字段,不如建个组合索引,别每个字段都单独建:
CREATE INDEX idx_users_email_username ON users (email, username);
这样能加速同时用email 和 username过滤的查询。
- 定期检查现有索引。 随着数据库变大,你的查询也会变。以前有用的索引现在可能没用了。记得定期检查,把没用的索引删掉。
举例怎么精简索引
回到我们的users表。与其建三个单独的索引,不如这样优化:
- 如果很少按
created_at排序,就别给它单独建索引。 - 把
email和username的索引合成一个组合索引:
CREATE INDEX idx_users_email_username ON users (email, username);
总结:怎么找到平衡点?
和编程里的很多事一样,这里也讲究“少即是多”。别因为能加索引就给每个字段都加。想清楚你为什么要加索引,它到底能不能让查询变快。务实点,记住:好开发不是乱加索引,而是懂得它们的影响,能用得恰到好处。
现在你有了这个技能,就能避免索引太多导致的灾难,让你的数据库飞快,像猎豹一样,而不是背着一堆没用索引的乌龟。
GO TO FULL VERSION