引言

PostgreSQL 作为全球最先进的开源关系型数据库,以其强大的功能、稳定性和扩展性著称。然而,许多开发者仅仅把它当作"另一个 MySQL"来用,未能发挥其真正威力。本文将从执行计划分析出发,深入探讨 PostgreSQL 查询优化的方方面面,涵盖索引策略、SQL 重写、分区方案、统计信息维护、Vacuum 机制,以及生产环境中的常见陷阱与解决方案。

一、执行计划分析:优化的起点

1.1 EXPLAIN 基础用法

PostgreSQL 提供了 EXPLAIN 命令来查看查询的执行计划。基础用法非常简单:

EXPLAIN SELECT * FROM orders WHERE user_id = 1000 AND created_at > '2024-01-01';

Seq Scan on orders  (cost=0.00..15406.00 rows=1 width=200)
  Filter: ((user_id = 1000) AND (created_at > '2024-01-01'::date))

输出中关键指标包括:

  • cost:估算的执行成本,格式为 启动成本..总成本
  • rows:预估返回行数
  • width:预估返回行的平均宽度(字节)

Seq Scan(顺序扫描)表示全表扫描,对于大表来说这是性能杀手。当看到 Seq Scan 时,通常意味着缺少合适的索引或查询条件没有利用索引。

1.2 EXPLAIN ANALYZE:获取真实运行数据

EXPLAIN ANALYZE 不仅显示执行计划,还会实际执行查询,提供真实的执行时间、行数等统计信息:

EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT o.id, o.total_amount, u.username
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'paid'
  AND o.created_at >= '2024-06-01'
ORDER BY o.created_at DESC
LIMIT 20;

常见的执行计划节点类型:

  • Seq Scan:全表扫描,逐行读取
  • Index Scan:索引扫描,通过索引定位数据行
  • Index Only Scan:仅索引扫描,所有需要的数据都在索引中,无需回表(覆盖索引)
  • Bitmap Index Scan:位图索引扫描,将多个索引结果合并为位图后批量取数据
  • Nested Loop:嵌套循环连接,适合小表关联
  • Hash Join:哈希连接,适合无索引的大表关联
  • Merge Join:归并连接,要求输入数据有序

1.3 使用 pg_stat_statements 发现慢查询

pg_stat_statements 扩展记录了所有 SQL 语句的执行统计信息。在 postgresql.conf 中启用:

# postgresql.conf
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.max = 10000
pg_stat_statements.track = all

安装扩展:

CREATE EXTENSION pg_stat_statements;

查找最耗时的查询:

SELECT queryid, substring(query, 1, 80) AS short_query,
       calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

1.4 auto_explain 模块

auto_explain 可以自动记录超过指定阈值的执行计划到日志:

# postgresql.conf
shared_preload_libraries = 'auto_explain'
auto_explain.log_min_duration = '2s'
auto_explain.log_analyze = true
auto_explain.log_buffers = true
auto_explain.log_timing = true

二、索引优化策略

2.1 索引类型选择指南

PostgreSQL 支持多种索引类型,选择合适的是优化的关键:

  • B-Tree(默认):支持 =、<、>、BETWEEN、IN、LIKE 'xxx%' 操作。最通用的索引类型,适合绝大多数场景。
  • Hash:仅支持等值查询(=),但比 B-Tree 更小更快。PostgreSQL 10+ 已支持 WAL 日志,可用于生产环境。
  • GIN(倒排索引):适合多值类型数据,如 jsonb、数组、全文搜索。典型场景:jsonb_path_ops 索引、数组包含查询(@>)。
  • GiST(通用搜索树):适合地理空间数据、范围类型、全文搜索。PostGIS 的空间索引基于 GiST。
  • BRIN(块范围索引):基于数据物理存储顺序的轻量级索引。适合按时间顺序写入、按时间范围查询的超大表(如日志、时序数据)。索引极小(B-Tree 的百分之一以下)。
  • SP-GiST(空间分区树):适合非平衡数据结构的搜索,如 IP 路由表、电话线路前缀匹配。

2.2 复合索引设计

B-Tree 复合索引遵循最左前缀原则,列的顺序直接影响索引的利用率:

-- 好的设计:将等值查询列放在前面,范围列放在后面
CREATE INDEX idx_orders_user_status_date 
ON orders (user_id, status, created_at);

-- 这个查询可以完全利用上述索引
SELECT * FROM orders 
WHERE user_id = 1000 
  AND status = 'paid' 
  AND created_at >= '2024-06-01'
  AND created_at < '2024-07-01';

复合索引的实用技巧:

  • 高选择性列在前:将唯一值多的列放在前面,可以更快过滤数据
  • WHERE 列在前,ORDER BY 列在后:如果索引列顺序匹配 ORDER BY,可以避免额外的排序操作
  • 利用索引跳过扫描:如果查询只用到复合索引的第二列,可以创建以该列为前导的新索引

2.3 覆盖索引(Index Only Scan)

PostgreSQL 11+ 支持 INCLUDE 子句创建覆盖索引,将不需要在搜索条件中的列放入叶子节点,避免回表:

CREATE INDEX idx_orders_user_date_amount 
ON orders (user_id, created_at) 
INCLUDE (total_amount, status);

当查询只涉及索引中所有的列时,PostgreSQL 可以直接从索引中返回数据(Index Only Scan),无需访问堆表,大幅提升性能。

2.4 部分索引(Partial Index)

只在表子集上创建索引,既不浪费索引空间,又能加速高频查询:

-- 只为活跃用户创建索引(假设 80% 查询集中在活跃用户)
CREATE INDEX idx_active_users_orders ON orders (user_id, created_at)
WHERE status IN ('pending', 'paid', 'shipped');

-- 只为未删除的记录创建唯一索引(排除软删除的行)
CREATE UNIQUE INDEX idx_unique_email ON users (email)
WHERE deleted_at IS NULL;

2.5 表达式索引和函数索引

对表达式或函数的结果建立索引,加速函数操作的查询:

-- 加速 LOWER(email) 查询(不区分大小写的用户查找)
CREATE INDEX idx_users_email_lower ON users (LOWER(email));

-- 加速日期范围查询
CREATE INDEX idx_orders_date ON orders (DATE(created_at));

-- 加速 JSONB 字段访问(PostgreSQL 12+)
CREATE INDEX idx_metadata ON products ((metadata->>'category'));

-- 多表达式组合
CREATE INDEX idx_products_search ON products (
    LOWER(name), 
    (price * 100)  -- 将 price 转为整数分加速比较
);

2.6 BRIN 索引实战

对于按时间顺序写入的超大表(如日志、事件流),BRIN 索引是最佳选择:

-- BRIN 索引大小仅为 B-Tree 的 1/100 ~ 1/1000
CREATE INDEX idx_events_timestamp_brin ON events 
USING BRIN (created_at) 
WITH (pages_per_range = 32);

-- 检查 BRIN 索引大小对比
SELECT indexrelname, pg_size_pretty(pg_relation_size(indexrelid))
FROM pg_stat_user_indexes 
WHERE schemaname = 'public' 
ORDER BY pg_relation_size(indexrelid) DESC;

pages_per_range 参数控制每个索引范围覆盖的物理块数。值越小,索引精度越高但体积越大;值越大,索引越小但扫描时需跳过更多行。对于严格按时间写入的表,32 是常用值。

三、SQL 重写技巧

3.1 避免在 WHERE 子句中使用函数或表达式

在列上使用函数会导致索引失效(除非有对应的表达式索引)。应尽量将计算移到常量一侧:

-- 无法使用索引(对 indexed_col 使用了函数)
SELECT * FROM orders WHERE YEAR(created_at) = 2024;
SELECT * FROM orders WHERE total > price * 1.1;

-- 可以使用索引(计算移到常量侧)
SELECT * FROM orders WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';

3.2 用 EXISTS 替代 IN

对于子查询,EXISTS 通常比 IN 更高效,因为 EXISTS 在找到第一个匹配行后即可停止:

-- IN 子查询(子查询结果会物化为一个列表)
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE total > 1000);

-- EXISTS(找到第一个匹配即停止)
SELECT * FROM users u 
WHERE EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.total > 1000
);

PostgreSQL 优化器对简单 IN 子查询会自动改写为半连接(Semi-Join),但复杂情况下的 EXISTS 仍然更可靠。

3.3 窗口函数替代自连接

LAG/LEAD 窗口函数通常比自连接更高效:

-- 自连接(性能差,每个日期都要做全表过滤)
SELECT a.date, a.sales - b.sales AS growth
FROM daily_sales a 
JOIN daily_sales b ON a.date = b.date + INTERVAL '1 day';

-- 窗口函数(单次扫描即可)
SELECT date, 
       sales - LAG(sales) OVER (ORDER BY date) AS growth
FROM daily_sales;

3.4 批量操作优化

使用 INSERT ... ON CONFLICT(UPSERT)替代先查后插:

-- 批量upsert(比逐条检查快10倍以上)
INSERT INTO inventory (product_id, warehouse_id, quantity)
VALUES (1, 'A', 100), (2, 'B', 200), (3, 'C', 150)
ON CONFLICT (product_id, warehouse_id) 
DO UPDATE SET quantity = inventory.quantity + EXCLUDED.quantity,
              updated_at = NOW();

3.5 CTE 的物化陷阱

PostgreSQL 12 之前,CTE(WITH 子查询)会被优化栅栏(optimization fence)包裹,强制物化结果,可能造成意外性能问题:

-- PG12 之前:CTE 结果会被先物化再参与外部查询
WITH active_users AS (
    SELECT id FROM users WHERE last_login > NOW() - INTERVAL '30 days'
)
SELECT o.* FROM orders o 
JOIN active_users u ON o.user_id = u.id 
WHERE o.total > 100;
-- active_users 会被独立执行,可能扫描全部活跃用户

解决方案:PostgreSQL 12+ 中,非递归且未使用 MATERIALIZED 的 CTE 会被自动内联优化。如果仍需要兼容旧版本,可以使用子查询替代 CTE:

-- 子查询替代 CTE(可被优化器内联)
SELECT o.* FROM orders o 
JOIN (
    SELECT id FROM users WHERE last_login > NOW() - INTERVAL '30 days'
) u ON o.user_id = u.id 
WHERE o.total > 100;

四、表分区方案

4.1 分区类型选择

PostgreSQL 10+ 支持原生声明式分区(推荐),PostgreSQL 13+ 引入了哈希分区:

  • RANGE 分区:按范围划分(如按日期),最常用的分区方式
  • LIST 分区:按离散值划分(如按地区ID)
  • HASH 分区:按哈希值均匀分布,适合没有自然分区键的大表

4.2 时间范围分区实战

-- 创建分区表
CREATE TABLE events (
    id BIGSERIAL,
    event_type VARCHAR(50) NOT NULL,
    payload JSONB NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT NOW()
) PARTITION BY RANGE (created_at);

-- 创建每月分区
CREATE TABLE events_2024_06 PARTITION OF events
    FOR VALUES FROM ('2024-06-01') TO ('2024-07-01');
CREATE TABLE events_2024_07 PARTITION OF events
    FOR VALUES FROM ('2024-07-01') TO ('2024-08-01');
CREATE TABLE events_2024_08 PARTITION OF events
    FOR VALUES FROM ('2024-08-01') TO ('2024-09-01');

-- 创建默认分区(捕获不匹配的行,防止插入失败)
CREATE TABLE events_default PARTITION OF events DEFAULT;

4.3 分区自动化管理

使用 pg_partman 扩展可以自动创建和清理分区:

-- 创建未来 3 个月的分区
SELECT partman.create_parent(
    p_parent_schema := 'public',
    p_parent_table := 'events',
    p_control := 'created_at',
    p_type := 'range',
    p_interval := '1 month',
    p_premake := 3
);

-- 自动清理 6 个月前的分区(或归档到冷存储)
UPDATE partman.part_config 
SET retention = '6 months', retention_keep_table = true
WHERE parent_table = 'public.events';

4.4 分区查询优化

分区表的查询性能取决于分区剪枝(Partition Pruning):

  • 查询条件中必须包含分区键,PostgreSQL 才能跳过无关分区
  • 使用 EXPLAIN 查看 -> Append 节点,确认只扫描了相关分区
  • 对分区键使用函数可能破坏分区剪枝,应保持原始列比较
-- 好的查询:分区剪枝生效,只扫描 1 个分区
SELECT count(*) FROM events 
WHERE created_at >= '2024-07-01' AND created_at < '2024-08-01';

-- 坏的查询:分区剪须扫描全部分区
SELECT count(*) FROM events 
WHERE EXTRACT(MONTH FROM created_at) = 7;
-- 修正为:WHERE created_at >= '2024-07-01' AND created_at < '2024-08-01'

五、高级优化特性

5.1 并行查询

PostgreSQL 9.6+ 支持并行顺序扫描,10+ 支持并行连接,11+ 支持并行索引扫描。通过以下配置启用:

# postgresql.conf
max_parallel_workers_per_gather = 4
max_parallel_workers = 8
parallel_tuple_cost = 0.01
parallel_setup_cost = 100
min_parallel_table_scan_size = '8MB'

查询是否并行由优化器和 parallel_tuple_cost 等参数控制,通常只对大表的扫描和连接生效。小表查询并行化反而更慢(协调开销)。

5.2 JIT 编译优化

PostgreSQL 11+ 支持 LLVM JIT 编译,对大量数据处理的查询有显著加速:

# postgresql.conf
jit = on
jit_above_cost = 100000
jit_inline_above_cost = 500000
jit_optimize_above_cost = 500000

JIT 适合聚合计算多、条件过滤复杂的分析型查询。对于简单 OLTP 查询,JIT 增加的编译开销可能超过收益。

5.3 物化视图

对于复杂且不需要实时结果的统计查询,可以使用物化视图:

CREATE MATERIALIZED VIEW sales_summary AS
SELECT product_id, 
       date_trunc('day', created_at) AS day,
       COUNT(*) AS order_count,
       SUM(total_amount) AS daily_revenue,
       AVG(total_amount) AS avg_order_value
FROM orders
WHERE status = 'completed'
GROUP BY product_id, date_trunc('day', created_at);

-- 创建唯一索引才能使用 CONCURRENTLY 刷新
CREATE UNIQUE INDEX ON sales_summary (product_id, day);

-- 不阻塞读写地刷新
REFRESH MATERIALIZED VIEW CONCURRENTLY sales_summary;

5.4 查询提示(pg_hint_plan)

PostgreSQL 官方不支持查询提示(与 Oracle/SQL Server 不同),但 pg_hint_plan 扩展能在注释中嵌入提示:

/*+ 
    SeqScan(o)
    IndexScan(u users_pkey)
    Leading((o u))
    NestLoop(o u)
*/
SELECT * FROM orders o 
JOIN users u ON o.user_id = u.id 
WHERE o.created_at > '2024-07-01';

六、表统计信息与 Vacuum 机制

PostgreSQL 优化器依赖统计信息(存储在 pg_statistic 系统表中)来估算查询代价。过时的统计信息会导致执行计划选择错误:

-- 查看某表的最后分析时间和估计行数
SELECT schemaname, tablename, last_analyze, last_autoanalyze,
       n_tup_ins, n_tup_upd, n_tup_del, n_live_tup, n_dead_tup
FROM pg_stat_user_tables 
WHERE tablename = 'orders';

-- 手动更新统计信息(可以指定列或采样比例)
ANALYZE orders;
ANALYZE orders (user_id, status);  -- 只更新特定列
ALTER TABLE orders ALTER COLUMN user_id SET STATISTICS 1000;  -- 增加采样率

6.2 Autovacuum 调优

PostgreSQL 使用 MVCC(多版本并发控制)实现事务隔离,UPDATE 和 DELETE 不会原地修改旧行,而是创建新版本。废弃的旧版本(dead tuples)需要 Vacuum 清理:

# postgresql.conf - 全局 autovacuum 配置
autovacuum = on
autovacuum_max_workers = 3
autovacuum_naptime = '30s'
autovacuum_vacuum_threshold = 50
autovacuum_vacuum_scale_factor = 0.1
autovacuum_analyze_threshold = 50
autovacuum_analyze_scale_factor = 0.05

对于高写入的大表,可以单独调优 autovacuum 参数:

ALTER TABLE orders SET (
    autovacuum_vacuum_scale_factor = 0.02,  -- 2% 的死元组就触发 VACUUM
    autovacuum_analyze_scale_factor = 0.01, -- 1% 的数据变更就触发 ANALYZE
    autovacuum_vacuum_cost_delay = 5,       -- 降低延迟让 VACUUM 更快
    autovacuum_vacuum_threshold = 1000      -- 最小死元组阈值
);

6.3 监控表膨胀

表膨胀(bloat)是死元组未被及时清理导致的空间浪费和性能下降:

-- 查看表的死元组和膨胀情况(需要 pgstattuple 扩展)
CREATE EXTENSION pgstattuple;
SELECT * FROM pgstattuple('orders');

-- 查看表的磁盘占用和元组状态
SELECT relname, n_live_tup, n_dead_tup,
       ROUND(n_dead_tup::numeric / NULLIF(n_live_tup, 0) * 100, 2) AS dead_pct
FROM pg_stat_user_tables 
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;

七、连接池与并发优化

7.1 PgBouncer 连接池

PostgreSQL 每个连接对应一个后端进程,大量并发连接会消耗大量内存。推荐在生产环境使用 PgBouncer:

# pgbouncer.ini
[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb

[pgbouncer]
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
reserve_pool_size = 5
reserve_pool_timeout = 3

池模式说明:

  • session:连接占用到断开,支持 PREPARE、临时表等
  • transaction:连接占用到事务结束(推荐),不支持连接级特性
  • statement:连接占用到单条语句(极少使用)

7.2 锁监控与死锁处理

-- 查看当前锁等待
SELECT blocked_locks.pid AS blocked_pid,
       blocked_activity.query AS blocked_query,
       blocking_locks.pid AS blocking_pid,
       blocking_activity.query AS blocking_query
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktype
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted;

-- 查看表级锁
SELECT t.relname, l.locktype, l.mode, l.granted, a.query
FROM pg_locks l
JOIN pg_stat_activity a ON l.pid = a.pid
JOIN pg_class t ON l.relation = t.oid
WHERE t.relname NOT LIKE 'pg_%'
ORDER BY t.relname;

八、生产环境运维最佳实践

8.1 内存参数调优

# postgresql.conf
shared_buffers = 8GB                    # 推荐设为物理内存的 25%
effective_cache_size = 24GB             # 推荐设为物理内存的 75%
maintenance_work_mem = 1GB              # 维护操作(CREATE INDEX、VACUUM)可用内存
work_mem = 64MB                         # 单个排序/哈希操作可用内存
wal_buffers = 64MB                      # WAL 缓冲区大小
random_page_cost = 1.1                  # SSD 推荐设为 1.1(默认 4.0 适合 HDD)
effective_io_concurrency = 200          # SSD 推荐设为 200

8.2 WAL 与归档配置

# postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'cp %p /var/lib/postgresql/archive/%f'
max_wal_size = 4GB
min_wal_size = 1GB
checkpoint_completion_target = 0.9

8.3 监控关键指标

生产环境建议建立以下监控项:

  • 连接数:pg_stat_activity 中的活跃连接数
  • 长事务:运行超过 5 分钟的事务
  • 锁等待:等待锁的会话数量
  • 缓存命中率:pg_stat_database 中的 blks_hit / (blks_hit + blks_read)(目标 > 99%)
  • 元组命中率:堆表访问效率
  • 复制延迟:主从库的字节或时间延迟
  • 死元组比例:触发 autovacuum 的频率
-- 检查点命中率
SELECT datname, 
       ROUND(blks_hit::numeric / (blks_hit + blks_read) * 100, 2) AS cache_hit_ratio
FROM pg_stat_database 
WHERE blks_read > 0;

-- 查看最耗 IO 的表和索引
SELECT schemaname, relname, 
       seq_scan, seq_tup_read,
       idx_scan, idx_tup_fetch,
       n_tup_ins, n_tup_upd, n_tup_del, n_live_tup, n_dead_tup
FROM pg_stat_user_tables 
ORDER BY seq_scan * seq_tup_read DESC
LIMIT 10;

8.4 pgBench 压力测试

# 初始化测试数据(100万行)
pgbench -i -s 50 mydb

# 运行 TPC-B 标准测试(60秒,8客户端)
pgbench -c 8 -T 60 mydb

# 混合业务模拟(自定义SQL文件)
pgbench -c 10 -j 4 -T 120 -f custom_benchmark.sql mydb

九、常见踩坑经验

坑1:OR 导致索引失效

-- 索引可能失效(OR 操作符导致)
SELECT * FROM orders WHERE user_id = 100 OR status = 'paid';

-- 改善方案:使用 UNION ALL 拆分
SELECT * FROM orders WHERE user_id = 100
UNION ALL
SELECT * FROM orders WHERE status = 'paid' AND user_id != 100;

坑2:LIKE '%xxx%' 不走索引

-- 无法使用B-Tree索引
SELECT * FROM users WHERE name LIKE '%张%';

-- 解决方案1:GIN 索引(pg_trgm 扩展)
CREATE EXTENSION pg_trgm;
CREATE INDEX idx_name_trgm ON users USING GIN (name gin_trgm_ops);
SELECT * FROM users WHERE name LIKE '%张%';  -- 现在使用索引

-- 解决方案2:前缀匹配可以用 B-Tree(仅限 LIKE 'xxx%')
CREATE INDEX idx_name ON users (name);
SELECT * FROM users WHERE name LIKE '张%';  -- 使用 B-Tree 索引

坑3:隐式类型转换导致索引失效

-- user_id 是 bigint,传入字符串会导致隐式转换
SELECT * FROM orders WHERE user_id = '100';  -- 可能不走索引

-- 确保类型一致
SELECT * FROM orders WHERE user_id = 100;     -- 正确

坑4:LIMIT 无 ORDER BY 导致结果不稳定

-- 没有 ORDER BY 的 LIMIT 每次结果可能不同
SELECT * FROM orders LIMIT 10;

-- 始终加上 ORDER BY 保证可重现性
SELECT * FROM orders ORDER BY id LIMIT 10;

坑5:OFFSET 分页在大偏移量时性能极差

-- OFFSET 1000000 需要扫描并丢弃前 100万行
SELECT * FROM orders ORDER BY id LIMIT 10 OFFSET 1000000;

-- 改善方案:游标分页(Keyset Pagination)
SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 10;

坑6:大批量 DELETE 导致表膨胀

-- 一次性删除 1亿行 → 表膨胀 + 锁等待
DELETE FROM logs WHERE created_at < '2023-01-01';

-- 改善方案:分批次删除(每次1万行)
DO $$
DECLARE
    batch_size INT := 10000;
    rows_affected INT := 1;
BEGIN
    WHILE rows_affected > 0 LOOP
        DELETE FROM logs WHERE id IN (
            SELECT id FROM logs WHERE created_at < '2023-01-01' LIMIT batch_size
        );
        GET DIAGNOSTICS rows_affected = ROW_COUNT;
        PERFORM pg_sleep(0.5);  -- 给其他事务留出时间
    END LOOP;
END $$;

十、总结

PostgreSQL 查询优化是一个系统工程,需要从执行计划分析入手,层层递进地排查性能瓶颈。核心优化思路可总结为"五步法":

  1. 发现问题:通过 pg_stat_statements、auto_explain 发现慢查询
  2. 理解计划:使用 EXPLAIN ANALYZE 理解每个节点的成本和瓶颈
  3. 索引优化:选择合适的索引类型,设计高效的复合索引、覆盖索引、部分索引
  4. SQL 重写:避免反逻辑写法(OR、函数包裹列、大 OFFSET),善用窗口函数和 CTE
  5. 系统调优:合理配置内存参数、autovacuum、连接池、分区策略

PostgreSQL 作为"最先进的开源数据库",隐藏的威力远不止于此。从全文搜索到 JSONB 处理,从 PostGIS 空间数据到 TimescaleDB 时序扩展,深入了解其特性和优化手段,能让你的数据库层不再是系统瓶颈,而成为竞争优势。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部