针对查询:
SELECT * FROM sync_log t WHERE t.customer_id = 1 AND t.user_id = 1 AND t.status = 'active' AND t.create_time < '2025-05-05';
索引优化建议
1. 核心优化原则

-
等值条件优先 :
customer_id、user_id、status都是等值过滤(=),应放在复合索引的前面。 -
范围条件靠后 :
create_time是范围过滤(<),应放在复合索引的最后。 -
充分利用索引过滤:索引应能快速定位到满足前三个等值条件的行,然后在索引内部扫描满足时间范围的记录,避免全表扫描。
2. 推荐的复合索引
CREATE INDEX idx_sync_log_cust_user_status_time ON sync_log (customer_id, user_id, status, create_time);
理由:
-
等值列
customer_id、user_id、status按任意顺序放在前面均可,但通常将区分度较高 的列放在更前面(例如customer_id通常比status区分度更高)。这里假设三列都参与等值过滤,实际执行时索引会从最左匹配到最后一个等值列,然后利用create_time进行范围扫描。 -
索引本身已经包含了查询条件中的所有列,但查询是
SELECT *,因此仍需回表获取其他字段。如果表中数据量极大且回表开销成为瓶颈,可以考虑将查询中频繁需要的字段也加入索引(覆盖索引),但会增加索引体积,需权衡。
3. 考虑部分索引(Partial Index)
如果 status 列的值非常有限(例如只有 'active'、'inactive'),且查询几乎总是针对 status = 'active',可以创建部分索引,减少索引大小,提升维护效率:
CREATE INDEX idx_sync_log_active_cust_user_time ON sync_log (customer_id, user_id, create_time) WHERE status = 'active';
优点:
-
索引只包含
status = 'active'的行,体积更小,扫描更快。 -
索引本身已经隐含了
status条件,因此索引键只需包含customer_id、user_id、create_time。
注意 :如果查询中 status 可能变为其他值,则部分索引无法使用,需保留普通索引。
4. 考虑索引列顺序的微调
如果 status 列的选择性非常差 (例如绝大多数行都是 'active'),那么将它放在索引前部可能意义不大,可以考虑将其放在 create_time 之前,但这不是最佳选择。更好的方案是:
-
仍然采用
(customer_id, user_id, create_time)作为索引,但需要确保status列也能被有效过滤。PostgreSQL 可以在索引扫描后通过status条件进行过滤,如果active行比例高,这样的过滤代价依然可以接受。 -
或者采用部分索引(如上述),直接规避低选择性列。
5. 验证与调优
创建索引后,使用 EXPLAIN ANALYZE 观察执行计划:
EXPLAIN ANALYZE SELECT * FROM sync_log WHERE customer_id = 1 AND user_id = 1 AND status = 'active' AND create_time < '2025-05-05';
期望看到 Index Scan 使用新创建的索引,而不是 Seq Scan。如果发现索引未被使用,可能原因:
-
统计信息陈旧:运行
ANALYZE sync_log;更新统计信息。 -
查询条件返回行数过多,优化器认为全表扫描更快(可调整
random_page_cost或enable_seqscan测试,但一般无需强制)。 -
索引顺序不合理:尝试调整等值列顺序,将区分度最高的列放在最前面。
6. 额外考虑:如果表非常大且并发高
-
可以考虑使用 BRIN索引 在
create_time上,如果数据按时间物理有序,可以快速跳过大量数据块,但通常需要结合其他条件使用,不如 B-tree 通用。 -
如果查询还涉及其他过滤条件,可根据实际业务模式进一步调整索引。
-
也可以增加一个年-月的字段,例如year_month=2026-02,那么我们把查询范围涉及到的月份作为in条件查询进来 SELECT * FROM sync_log WHERE customer_id = 1 AND user_id = 1 AND status = 'active' and year_month='2026-02' AND create_time < '2025-05-05';