AgensGraph 完全指南 / 第 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_cost | 1.0 | 顺序页面读取代价 |
random_page_cost | 4.0 | 随机页面读取代价(SSD 可降至 1.1) |
cpu_tuple_cost | 0.01 | 处理每行的 CPU 代价 |
cpu_index_tuple_cost | 0.005 | 处理索引元组的代价 |
cpu_operator_cost | 0.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_buffers | RAM × 25% | 共享缓冲区 |
effective_cache_size | RAM × 75% | 缓存估算 |
work_mem | 16-64MB | 排序/哈希内存 |
maintenance_work_mem | 512MB-2GB | 维护操作内存 |
max_connections | 200 | 最大连接数(连接池替代) |
max_parallel_workers_per_gather | 2-4 | 并行查询工作进程 |
max_parallel_workers | 8 | 最大并行工作进程 |
random_page_cost | 1.1 (SSD) / 4.0 (HDD) | 随机读代价 |
effective_io_concurrency | 200 (SSD) / 2 (HDD) | 并发 I/O 数 |
checkpoint_completion_target | 0.9 | 检查点平滑度 |
max_wal_size | 4GB | WAL 最大大小 |
wal_compression | on | WAL 压缩 |
autovacuum_max_workers | 3 | 自动 VACUUM 工作进程 |
autovacuum_vacuum_scale_factor | 0.05 | 触发 VACUUM 的死行比例 |
log_min_duration_statement | 1000 | 记录慢查询(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 基准测试结果记录模板
| 测试项 | 配置 | QPS | P50 延迟 | 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 练习
- 使用
EXPLAIN ANALYZE 分析一个 3 跳查询并解释各步骤的代价。 - 编写一个脚本自动检测并报告缓冲区命中率低于 99% 的告警。
- 为你的 AgensGraph 实例制定一份基于硬件配置的参数调优方案。
- 设计并运行一组基准测试,记录不同并发下的性能表现。
11.12 扩展阅读