强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 11 章:性能调优

第 11 章:性能调优

11.1 性能调优方法论

11.1.1 调优分层模型

性能调优分层:

  ┌──────────────────────────────────────┐
  │  1. 查询层    │ 优化 Cypher/SQL 语句  │  收益: ⭐⭐⭐⭐⭐
  ├───────────────┼──────────────────────┤
  │  2. 索引层    │ 创建合适的索引        │  收益: ⭐⭐⭐⭐⭐
  ├───────────────┼──────────────────────┤
  │  3. 模式层    │ 优化数据模型/Schema   │  收益: ⭐⭐⭐⭐
  ├───────────────┼──────────────────────┤
  │  4. 配置层    │ 调整数据库参数        │  收益: ⭐⭐⭐
  ├───────────────┼──────────────────────┤
  │  5. 硬件层    │ 升级 CPU/内存/SSD    │  收益: ⭐⭐
  └───────────────┴──────────────────────┘

  优化顺序: 1 → 2 → 3 → 4 → 5(从高收益低成本开始)

11.1.2 性能度量指标

指标说明目标值
查询延迟 (Latency)单次查询响应时间P95 < 100ms
吞吐量 (Throughput)每秒处理的查询数 (QPS)依业务需求
缓冲区命中率缓存命中比例> 99%
事务锁等待等待锁的时间占比< 1%
CPU 利用率CPU 使用率< 70%
磁盘 I/O物理读写次数尽量低

11.2 查询优化器

11.2.1 优化器工作原理

优化器决策过程:

  查询文本
    │
    ▼
  ┌──────────────┐
  │  解析器       │ → AST
  └──────┬───────┘
         ▼
  ┌──────────────┐
  │  重写器       │ → 优化后的 AST(视图展开、子查询转换)
  └──────┬───────┘
         ▼
  ┌──────────────┐
  │  规划器       │ → 生成所有可能的执行计划
  │  (Planner)   │   估算每个计划的代价
  │              │   选择最低代价的计划
  └──────┬───────┘
         ▼
  最优执行计划

11.2.2 代价模型

-- 查看查询的估算代价
EXPLAIN (COSTS ON, VERBOSE)
MATCH (p:Person {name: 'Alice'})-[:KNOWS*1..3]->(fof:Person)
RETURN DISTINCT fof.name;

-- 代价公式(概念性):
-- 总代价 = CPU 代价 + I/O 代价
-- CPU 代价 = 行数 × 每行处理代价
-- I/O 代价 = 随机页访问 × 随机页代价 + 顺序页访问 × 顺序页代价
参数默认值说明
seq_page_cost1.0顺序页面读取代价
random_page_cost4.0随机页面读取代价(SSD 可降至 1.1)
cpu_tuple_cost0.01处理每行的 CPU 代价
cpu_index_tuple_cost0.005处理索引元组的代价
cpu_operator_cost0.0025操作符/函数执行代价

11.2.3 针对 SSD 的优化

-- SSD 环境:随机读写代价接近顺序读写
ALTER SYSTEM SET random_page_cost = 1.1;
ALTER SYSTEM SET effective_io_concurrency = 200;
ALTER SYSTEM SET seq_page_cost = 1.0;
SELECT pg_reload_conf();

11.3 内存管理

11.3.1 内存区域配置

# =============================================
# 共享内存
# =============================================
shared_buffers = 4GB              # 共享缓冲区(RAM 的 25%)
huge_pages = try                  # 大页支持(减少 TLB 缺失)
wal_buffers = 64MB                # WAL 缓冲区

# =============================================
# 每连接工作内存
# =============================================
work_mem = 32MB                   # 排序/哈希操作内存
hash_mem_multiplier = 2.0         # 哈希操作的额外倍数
maintenance_work_mem = 1GB        # VACUUM/CREATE INDEX 内存

# =============================================
# 优化器内存估算
# =============================================
effective_cache_size = 12GB       # OS 缓存估算(RAM 的 75%)

11.3.2 work_mem 调优

-- work_mem 影响的操作:
-- ORDER BY, DISTINCT, JOIN (Hash Join), GROUP BY, 聚合

-- 查看排序操作的内存使用
EXPLAIN (ANALYZE, BUFFERS)
MATCH (p:Person) RETURN p.name ORDER BY p.age;

-- 在 sort 节点查看:
--   Sort Method: quicksort  Memory: 25kB  ← 内存排序
--   Sort Method: external merge  Disk: 1024kB  ← 磁盘排序(work_mem 不足)

-- 按查询设置 work_mem
SET work_mem = '64MB';
-- 执行复杂排序查询
RESET work_mem;
work_mem 大小适用场景风险
4MB轻量 OLTP排序可能溢出磁盘
16-32MB一般场景推荐默认值
64-256MB复杂分析查询大量连接时可能 OOM
512MB+批处理任务仅限单用户场景

注意work_mem每操作内存,一个复杂查询中多个排序操作会各自使用 work_mem。总内存消耗 = work_mem × 并发查询数 × 每查询排序操作数。


11.4 关键配置参数

11.4.1 完整参数参考

参数推荐值说明
shared_buffersRAM × 25%共享缓冲区
effective_cache_sizeRAM × 75%缓存估算
work_mem16-64MB排序/哈希内存
maintenance_work_mem512MB-2GB维护操作内存
max_connections200最大连接数(连接池替代)
max_parallel_workers_per_gather2-4并行查询工作进程
max_parallel_workers8最大并行工作进程
random_page_cost1.1 (SSD) / 4.0 (HDD)随机读代价
effective_io_concurrency200 (SSD) / 2 (HDD)并发 I/O 数
checkpoint_completion_target0.9检查点平滑度
max_wal_size4GBWAL 最大大小
wal_compressiononWAL 压缩
autovacuum_max_workers3自动 VACUUM 工作进程
autovacuum_vacuum_scale_factor0.05触发 VACUUM 的死行比例
log_min_duration_statement1000记录慢查询(ms)

11.4.2 根据服务器规模的配置模板

小规模(8GB RAM, 4核)

shared_buffers = 2GB
effective_cache_size = 6GB
work_mem = 16MB
maintenance_work_mem = 512MB
max_connections = 100
max_parallel_workers_per_gather = 2

中等(32GB RAM, 16核)

shared_buffers = 8GB
effective_cache_size = 24GB
work_mem = 32MB
maintenance_work_mem = 1GB
max_connections = 200
max_parallel_workers_per_gather = 4
max_parallel_workers = 8

大规模(128GB RAM, 64核)

shared_buffers = 32GB
effective_cache_size = 96GB
work_mem = 64MB
maintenance_work_mem = 4GB
max_connections = 500
max_parallel_workers_per_gather = 8
max_parallel_workers = 16

11.5 图查询性能优化

11.5.1 查询重写技巧

-- ❌ 低效: 先遍历再过滤
MATCH (p:Person)-[:KNOWS]->(f:Person)-[:WORKS_AT]->(c:Company)
WHERE p.name = 'Alice' AND c.name = 'TechCorp'
RETURN f.name;

-- ✅ 高效: 先定位再遍历
MATCH (p:Person {name: 'Alice'})-[:KNOWS]->(f:Person)-[:WORKS_AT]->(c:Company {name: 'TechCorp'})
RETURN f.name;

-- ❌ 低效: 无界变长路径
MATCH (p:Person {name: 'Alice'})-[:KNOWS*]->(target:Person)
RETURN target.name;

-- ✅ 高效: 有界变长路径
MATCH (p:Person {name: 'Alice'})-[:KNOWS*1..5]->(target:Person)
RETURN DISTINCT target.name;

-- ❌ 低效: 返回大量数据
MATCH (p:Person)-[:KNOWS]->(f:Person)
RETURN p, f;

-- ✅ 高效: 只返回需要的字段
MATCH (p:Person)-[:KNOWS]->(f:Person)
RETURN p.name, f.name
LIMIT 100;

11.5.2 常见优化场景

场景问题优化方案
全图扫描MATCH (n) RETURN n添加标签和属性过滤
深度遍历*1..20限制深度 + shortestPath
笛卡尔积多个 MATCH 交叉合并模式或使用 WITH
大量返回无 LIMIT添加 LIMIT 和分页
重复路径变长路径产生重复DISTINCT 或 simplePath()

11.6 连接池优化

11.6.1 PgBouncer 配置

; pgbouncer.ini
[databases]
agens_graph = host=127.0.0.1 port=5432 dbname=agens

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt

; 连接池模式
pool_mode = transaction          ; 事务级连接池(推荐)
; pool_mode = session            ; 会话级连接池

; 连接限制
default_pool_size = 50           ; 每用户/数据库对的连接池大小
max_client_conn = 1000           ; 最大客户端连接数
min_pool_size = 10               ; 最小保持连接数
reserve_pool_size = 5            ; 保留连接(突发流量)

; 超时设置
server_idle_timeout = 600        ; 空闲连接超时
client_idle_timeout = 0          ; 客户端空闲超时
query_timeout = 300              ; 查询超时(秒)

11.6.2 连接池监控

-- 通过 PgBouncer 管理接口查看状态
-- psql -h 127.0.0.1 -p 6432 pgbouncer

SHOW POOLS;
SHOW STATS;
SHOW CLIENTS;
SHOW SERVERS;

11.7 基准测试

11.7.1 测试数据生成

-- 生成 10 万个人物节点
UNWIND range(1, 100000) AS i
CREATE (:Person {
  name: 'Person_' + toString(i),
  age: 20 + (i % 50),
  city: CASE
    WHEN i % 5 = 0 THEN '北京'
    WHEN i % 5 = 1 THEN '上海'
    WHEN i % 5 = 2 THEN '广州'
    WHEN i % 5 = 3 THEN '深圳'
    ELSE '杭州'
  END
});

-- 生成 50 万条关系
MATCH (p1:Person), (p2:Person)
WHERE id(p2) = id(p1) + 1 AND rand() < 0.01
CREATE (p1)-[:KNOWS {since: 2020 + (rand() * 4 | 0)}]->(p2);

11.7.2 基准查询测试

-- 使用 pgbench 进行基准测试
-- 创建测试脚本 test_query.sql
-- \set person_id random(1, 100000)
-- SET graph_path = benchmark;
-- MATCH (p:Person)-[:KNOWS*1..3]->(fof:Person) WHERE id(p) = :person_id RETURN count(fof);

-- 运行基准测试
-- pgbench -c 10 -j 2 -T 60 -f test_query.sql agens

-- 记录结果
-- tps = 1500 (transactions per second)
-- latency avg = 6.5ms
-- latency stddev = 2.1ms

11.7.3 基准测试结果记录模板

测试项配置QPSP50 延迟P95 延迟P99 延迟
点查 (Point Query)1 并发
点查 (Point Query)50 并发
2 跳遍历1 并发
2 跳遍历50 并发
3 跳遍历1 并发
3 跳遍历50 并发
混合负载50 并发

11.8 慢查询诊断

11.8.1 开启慢查询日志

-- 记录执行超过 1 秒的查询
ALTER SYSTEM SET log_min_duration_statement = 1000;
-- 记录所有语句的执行时间
ALTER SYSTEM SET log_statement_stats = on;
SELECT pg_reload_conf();

11.8.2 pg_stat_statements 扩展

-- 启用 pg_stat_statements
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- 查看最慢的查询
SELECT
    query,
    calls,
    total_exec_time / 1000 AS total_seconds,
    mean_exec_time / 1000 AS avg_seconds,
    max_exec_time / 1000 AS max_seconds,
    rows,
    100.0 * shared_blks_hit / nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

-- 重置统计
SELECT pg_stat_statements_reset();

11.8.3 等待事件分析

-- 查看当前等待事件
SELECT
    pid,
    wait_event_type,
    wait_event,
    state,
    query
FROM pg_stat_activity
WHERE wait_event IS NOT NULL AND state = 'active';

-- 常见等待事件:
-- LWLock     → 轻量级锁(缓冲区、WAL)
-- Lock       → 重量级锁(行锁、表锁)
-- BufferPin  → 缓冲区固定
-- IO         → 磁盘 I/O
-- Client     → 等待客户端

11.9 自动化监控

11.9.1 关键监控指标

-- 缓冲区命中率(应 > 99%)
SELECT
    sum(blks_hit) AS hits,
    sum(blks_read) AS reads,
    round(100.0 * sum(blks_hit) / (sum(blks_hit) + sum(blks_read)), 2) AS hit_ratio
FROM pg_stat_database
WHERE datname = current_database();

-- 事务统计
SELECT
    datname,
    xact_commit AS commits,
    xact_rollback AS rollbacks,
    deadlocks,
    temp_files,
    pg_size_pretty(temp_bytes) AS temp_bytes
FROM pg_stat_database
WHERE datname = current_database();

-- 表膨胀检测
SELECT
    schemaname,
    relname,
    n_live_tup,
    n_dead_tup,
    round(100.0 * n_dead_tup / nullif(n_live_tup + n_dead_tup, 0), 2) AS dead_ratio
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;

11.10 本章小结

要点说明
调优顺序查询 → 索引 → 模式 → 配置 → 硬件
内存配置shared_buffers 25% RAM, work_mem 16-64MB
SSD 优化random_page_cost = 1.1
连接池使用 PgBouncer,pool_mode = transaction
监控pg_stat_statements + 慢查询日志
基准测试定期运行,记录趋势

11.11 练习

  1. 使用 EXPLAIN ANALYZE 分析一个 3 跳查询并解释各步骤的代价。
  2. 编写一个脚本自动检测并报告缓冲区命中率低于 99% 的告警。
  3. 为你的 AgensGraph 实例制定一份基于硬件配置的参数调优方案。
  4. 设计并运行一组基准测试,记录不同并发下的性能表现。

11.12 扩展阅读