就像再牛的跑车你往油箱里灌柠檬汽水也开不动,PostgreSQL 的索引也是这样。它确实很强大,但得会用才行。来看看用索引时常见的几个问题吧。
问题 1:索引太多了
先复习下前面讲过的内容。如果你在一张表上建了太多索引,PostgreSQL 每次都得维护它们,保证它们是最新的。这直接影响到写入、更新和删除操作。每个索引都得更新和同步!
比如说,我们有个表叫 students:
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(255) UNIQUE,
age INTEGER,
grade INTEGER
);
你想着“以防万一”给每个字段都建个索引:
CREATE INDEX idx_students_name ON students(name);
CREATE INDEX idx_students_age ON students(age);
CREATE INDEX idx_students_grade ON students(grade);
现在想象下你要插入一万条新数据。PostgreSQL 不光要往表里写数据,还得更新这三个索引。数据一多,写入速度就慢下来了,系统性能也跟着掉。
怎么避免这个问题? 建索引前先问自己两个问题:
- 这个字段在过滤(
WHERE)、排序(ORDER BY)或分组(GROUP BY)里用得多吗? - 查询会用到这个索引,还是最后还是全表扫描?
如果这两个问题的答案都是“很少”或者“几乎不用”,那就别建索引了。
问题 2:选错字段建索引
在低区分度的数据上建索引,就像往盖死的杯子里倒茶——基本没啥用。如果某个字段只有两三个不同的值,PostgreSQL 很可能直接全表扫描,根本不用索引。
比如我们有个表 courses:
CREATE TABLE courses (
id SERIAL PRIMARY KEY,
name VARCHAR(255),
level VARCHAR(10) -- 只能是 'Beginner', 'Intermediate' 或 'Advanced'
);
你想在 level 字段上建索引:
CREATE INDEX idx_courses_level ON courses(level);
但像这样的查询:
SELECT * FROM courses WHERE level = 'Beginner';
可能不会用索引,因为 PostgreSQL 觉得直接全表扫描更快。尤其是表小或者字段值不多的时候更是这样。
所以索引更适合高基数(就是唯一值多)的字段。低区分度的数据建议用别的优化方式,比如表分区。
问题 3:过时的索引
有时候索引建了,后来不用了也没删,就像桌面上的文件,刚开始没几个,后来一堆,找东西还得一个个翻,浪费时间……是不是很熟悉?
比如说,之前为了老功能建了个索引,后来改了查询逻辑又加了新索引。老索引没人用了,但还在那儿占空间,还拖慢写入。
避免这种情况,得定期检查和分析索引。PostgreSQL 有个很方便的指标:
SELECT
relname AS table_name,
indexrelname AS index_name,
idx_scan AS total_scans
FROM
pg_stat_user_indexes
WHERE
idx_scan = 0;
这里 idx_scan 表示这个索引被用过多少次。如果是 0,说明没用过,可以删掉:
DROP INDEX idx_courses_level;
问题 4:经常更新的字段建了索引
如果你在经常更新的字段上建索引,PostgreSQL 每次改数据都得重建索引,性能会大受影响。
比如有个订单表:
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
status VARCHAR(20), -- 可能会变好几次(比如 "新建", "处理中", "已完成")
total NUMERIC(10, 2)
);
你为了加速按状态查找,给 status 字段建了索引:
CREATE INDEX idx_orders_status ON orders(status);
但如果 status 每条记录都要改好几次,这个索引反而拖慢性能。
想避免这种情况,别在经常变的字段上建索引。如果确实需要索引,可以考虑用部分索引:
CREATE INDEX idx_orders_status_partial
ON orders(status)
WHERE status = '处理中';
这样只有指定值的记录才会被索引,更新时压力小很多。
问题 5:在没必要的字段上加 UNIQUE 限制
唯一索引(UNIQUE)是为了保证数据唯一性自动建的。但如果唯一性不是必须的,这些索引只会增加负担。
比如我们有个日志表:
CREATE TABLE logs (
id SERIAL PRIMARY KEY,
message TEXT,
created_at TIMESTAMP UNIQUE
);
如果每秒插入成千上万条数据,created_at 的唯一性校验会让系统压力山大。
想让系统轻松点,只有真的需要唯一性时才加 UNIQUE。像上面这种情况,如果 created_at 不一定唯一,直接换成普通索引:
CREATE INDEX idx_logs_created_at ON logs(created_at);
问题 6:组合索引用错了
组合索引(multi-column indexes)在查询要同时按多个字段过滤或排序时很有用。但得建对了,不然根本用不上。
比如我们有个索引:
CREATE INDEX idx_students_name_grade ON students(name, grade);
这个索引会在查询同时按 name 和 grade 过滤或排序时用到:
SELECT * FROM students WHERE name = 'Alice' AND grade = 90;
但像这样:
SELECT * FROM students WHERE grade = 90;
就用不上这个索引了,因为 name 是第一个字段。
所以建组合索引时,顺序要和查询里用的顺序一致。如果只按某个字段过滤,建议单独建索引。
实用建议
监控索引的使用情况。PostgreSQL 有个系统视图 pg_stat_user_indexes,可以看到哪些索引在用,哪些没用。
优化查询和索引一起上。烂查询加再多索引也没用。
记得清理。过时的索引只会占空间,还拖慢写入。
就这些啦,朋友们!索引确实很强,但记住能力越大责任越大。用索引要有意识,这样你的数据库就能像 SpaceX 的火箭一样飞起来!
GO TO FULL VERSION