第 9 章:性能优化
第 9 章:性能优化
掌握 rqlite 的性能特征,优化读写性能、批量操作和连接管理。
9.1 性能特征概述
rqlite 的性能受以下因素影响:
| 因素 | 影响程度 | 说明 |
|---|---|---|
| 网络延迟 | ⭐⭐⭐ | Raft 复制需要网络往返 |
| 磁盘 I/O | ⭐⭐⭐ | Raft 日志和 SQLite 写入 |
| 批量大小 | ⭐⭐⭐ | 批量操作显著提升吞吐量 |
| 一致性级别 | ⭐⭐ | none 比 strong 快 |
| 数据规模 | ⭐⭐ | SQLite 索引和查询复杂度 |
| 节点数量 | ⭐ | 更多节点 = 更多复制开销 |
典型性能数据
| 操作类型 | 单条延迟 | 批量(100条)延迟 | 吞吐量 |
|---|---|---|---|
| INSERT (单条) | ~2ms | — | ~500 ops/s |
| INSERT (批量) | — | ~20ms | ~5000 ops/s |
| SELECT (none) | ~0.5ms | — | ~2000 ops/s |
| SELECT (weak) | ~1ms | — | ~1000 ops/s |
| SELECT (strong) | ~2ms | — | ~500 ops/s |
| UPDATE (单条) | ~2ms | — | ~500 ops/s |
提示: 以上数据基于 3 节点集群、同机房部署、数据量 < 1GB 的场景。
9.2 写入优化
9.2.1 批量写入
批量写入是提升 rqlite 写入性能的最有效手段:
# ❌ 逐条写入 — 性能差
for i in $(seq 1 100); do
curl -s -XPOST 'localhost:4001/db/execute' \
-H 'Content-Type: application/json' \
-d "[[\"INSERT INTO logs (message) VALUES (?)\", \"log entry $i\"]]"
done
# 耗时:~200ms × 100 = ~20s
# ✅ 批量写入 — 性能好
stmts=""
for i in $(seq 1 100); do
stmts+="{\"q\": \"INSERT INTO logs (message) VALUES (?)\", \"v\": [\"log entry $i\"]},"
done
stmts="${stmts%,}" # 去掉最后的逗号
curl -s -XPOST 'localhost:4001/db/execute' \
-H 'Content-Type: application/json' \
-d "[${stmts}]"
# 耗时:~20ms
9.2.2 批量大小建议
| 批量大小 | 延迟 | 吞吐量 | 适用场景 |
|---|---|---|---|
| 1 | ~2ms | ~500/s | 实时单条写入 |
| 10 | ~5ms | ~2000/s | 一般业务 |
| 50 | ~12ms | ~4000/s | 高吞吐需求 |
| 100 | ~20ms | ~5000/s | 数据导入 |
| 500 | ~80ms | ~6000/s | 大批量导入 |
| 1000 | ~150ms | ~6500/s | 极限批量 |
注意: 单次请求的语句数量不应超过 5000 条。过大的批次可能导致请求超时。
9.2.3 使用事务优化写入
-- 将多条写入放在一个事务中
BEGIN;
INSERT INTO logs (ts, msg) VALUES ('2026-05-10 10:00:00', 'msg1');
INSERT INTO logs (ts, msg) VALUES ('2026-05-10 10:00:01', 'msg2');
INSERT INTO logs (ts, msg) VALUES ('2026-05-10 10:00:02', 'msg3');
COMMIT;
rqlite 默认将单请求中的多条语句包装在事务中,无需显式 BEGIN/COMMIT。
9.2.4 预编译语句缓存
虽然 rqlite 本身不缓存预编译语句,但客户端可以通过连接池复用 HTTP 连接来减少开销:
# Python — 复用 requests.Session
import requests
session = requests.Session()
session.auth = ('admin', 'password')
# 复用 TCP 连接
for batch in batches:
session.post('http://localhost:4001/db/execute', json=batch)
9.3 读取优化
9.3.1 选择合适的一致性级别
# 场景 1: 统计查询(允许短暂不一致)→ none
curl -G 'localhost:4001/db/query' \
--data-urlencode 'q=SELECT COUNT(*) FROM orders' \
--data-urlencode 'level=none'
# 场景 2: 一般业务查询 → weak
curl -G 'localhost:4001/db/query' \
--data-urlencode 'q=SELECT * FROM users WHERE id = 1' \
--data-urlencode 'level=weak'
# 场景 3: 写后读 → strong
curl -G 'localhost:4001/db/query' \
--data-urlencode 'q=SELECT * FROM users WHERE id = LAST_INSERT_ID()' \
--data-urlencode 'level=strong'
9.3.2 使用索引优化查询
# 分析查询执行计划
rq 'q=EXPLAIN QUERY PLAN SELECT * FROM orders WHERE user_id = 1 AND status = "pending"'
索引设计原则:
| 原则 | 说明 | 示例 |
|---|---|---|
| 覆盖高频查询 | 为 WHERE 条件创建索引 | CREATE INDEX idx ON orders(user_id) |
| 复合索引顺序 | 选择性高的列在前 | CREATE INDEX idx ON orders(status, user_id) |
| 避免过多索引 | 每个索引增加写入开销 | 只创建必要的索引 |
| 使用覆盖索引 | 索引包含所有查询列 | CREATE INDEX idx ON orders(user_id, status, product) |
# 创建优化索引
rxe '[
["CREATE INDEX IF NOT EXISTS idx_orders_user_status ON orders(user_id, status)"],
["CREATE INDEX IF NOT EXISTS idx_orders_created ON orders(created_at DESC)"],
["CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)"]
]'
9.3.3 查询优化技巧
# ❌ 避免 SELECT * — 只查需要的列
rq 'q=SELECT id, username, email FROM users WHERE id = 1'
# ❌ 避免 LIKE 前缀通配符
rq 'q=SELECT * FROM users WHERE username LIKE "zhang%"' # ✅ 可以用索引
rq 'q=SELECT * FROM users WHERE username LIKE "%san%"' # ❌ 无法用索引
# ✅ 使用 LIMIT 限制返回数据
rq 'q=SELECT * FROM orders ORDER BY created_at DESC LIMIT 20'
# ✅ 使用覆盖索引
rq 'q=SELECT user_id, status, COUNT(*) FROM orders GROUP BY user_id, status'
9.3.4 读写分流
利用 Follower 节点分担读取压力:
┌────────────────────────────────────────────┐
│ 负载均衡器 │
│ ┌──────────────┬──────────────┐ │
│ │ 写请求 │ 读请求 │ │
│ │ → Leader │ → Follower │ │
│ └──────┬───────┴──────┬──────┘ │
│ │ │ │
│ ┌─────▼─────┐ ┌────▼──────┐ │
│ │ Leader │ │ Follower │ │
│ │ (node1) │ │ (node2) │ │
│ └───────────┘ └───────────┘ │
└────────────────────────────────────────────┘
# Nginx 配置示例 — 读写分流
upstream rqlite_write {
server 10.0.1.10:4001; # Leader
}
upstream rqlite_read {
server 10.0.1.10:4001; # Leader
server 10.0.1.11:4001; # Follower
server 10.0.1.12:4001; # Follower
}
server {
# 写请求路由到 Leader
location /db/execute {
proxy_pass http://rqlite_write;
}
# 读请求路由到任意节点
location /db/query {
proxy_pass http://rqlite_read;
}
}
9.4 索引优化策略
9.4.1 索引性能对比
# 无索引查询
rxe '[["DROP INDEX IF EXISTS idx_orders_user_status"]]'
# 10 万行数据,查询耗时 ~50ms
# 有索引查询
rxe '[["CREATE INDEX idx_orders_user_status ON orders(user_id, status)"]]'
# 10 万行数据,查询耗时 ~1ms
9.4.2 索引维护
# 查看表的索引
rq 'q=SELECT name, sql FROM sqlite_master WHERE type="index" AND tbl_name="orders"'
# 重建索引(碎片整理)
rxe '[["REINDEX idx_orders_user_status"]]'
# 删除未使用的索引
rxe '[["DROP INDEX IF EXISTS idx_unused"]]'
9.4.3 分析查询性能
# EXPLAIN QUERY PLAN
curl -s -G 'localhost:4001/db/query' \
--data-urlencode 'q=EXPLAIN QUERY PLAN SELECT u.username, o.product FROM users u JOIN orders o ON u.id = o.user_id WHERE u.id = 1'
9.5 SQLite PRAGMA 调优
# 查看当前配置
rq 'q=PRAGMA cache_size'
rq 'q=PRAGMA page_size'
rq 'q=PRAGMA journal_mode'
rq 'q=PRAGMA synchronous'
rq 'q=PRAGMA temp_store'
| PRAGMA | 默认值 | 优化值 | 说明 |
|---|---|---|---|
cache_size | -2000 | -10000 | 缓存页数,负值表示 KB |
page_size | 4096 | 4096 | 一般不需要改 |
journal_mode | WAL | WAL | rqlite 默认启用 |
synchronous | FULL | NORMAL | 降低同步级别可提升性能 |
temp_store | DEFAULT | MEMORY | 临时表使用内存 |
注意: PRAGMA 修改通过 rqlite API 执行后只对当前 Leader 的 SQLite 实例生效,不会通过 Raft 复制。启动参数才是可靠配置。
9.6 连接管理
9.6.1 HTTP 连接池
# Python — 使用 Session 复用连接
import requests
from requests.adapters import HTTPAdapter
session = requests.Session()
adapter = HTTPAdapter(
pool_connections=10, # 连接池大小
pool_maxsize=20, # 最大连接数
max_retries=3 # 重试次数
)
session.mount('http://', adapter)
session.mount('https://', adapter)
session.auth = ('admin', 'password')
// Go — 自定义 Transport
import (
"net/http"
"time"
)
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 20,
IdleConnTimeout: 90 * time.Second,
},
Timeout: 30 * time.Second,
}
9.6.2 超时配置
# 设置合理的超时
curl --connect-timeout 5 --max-time 30 \
-XPOST 'localhost:4001/db/execute' \
-H 'Content-Type: application/json' \
-d '[["INSERT INTO ..."]]'
# 查询超时参数
curl -G 'localhost:4001/db/query' \
--data-urlencode 'q=SELECT * FROM large_table' \
--data-urlencode 'timeout=10s'
9.7 数据量优化
9.7.1 数据归档
# 将旧数据归档
rxe '[
["CREATE TABLE IF NOT EXISTS orders_archive AS SELECT * FROM orders WHERE 0=1"],
["INSERT INTO orders_archive SELECT * FROM orders WHERE created_at < \"2025-01-01\""],
["DELETE FROM orders WHERE created_at < \"2025-01-01\""],
["VACUUM"]
]'
9.7.2 定期 VACUUM
# VACUUM 回收已删除数据占用的空间
rxe '[["VACUUM"]]'
注意: VACUUM 会临时占用约两倍磁盘空间,建议在低峰期执行。
9.8 性能测试工具
9.8.1 简单基准测试脚本
#!/bin/bash
# rqlite-bench.sh — 简单的 rqlite 性能测试
HOST="${RQLITE_HOST:-localhost:4001}"
TOTAL=${1:-1000}
BATCH=${2:-50}
echo "=== rqlite 性能测试 ==="
echo "目标: $HOST"
echo "总记录数: $TOTAL"
echo "批次大小: $BATCH"
echo ""
# 准备表
curl -s -XPOST "http://$HOST/db/execute" \
-H 'Content-Type: application/json' \
-d '[["CREATE TABLE IF NOT EXISTS bench (id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT, ts DATETIME DEFAULT CURRENT_TIMESTAMP)"]]' > /dev/null
# 批量写入测试
echo "--- 批量写入测试 ---"
start=$(date +%s%N)
inserted=0
while [ $inserted -lt $TOTAL ]; do
remaining=$((TOTAL - inserted))
current_batch=$((remaining < BATCH ? remaining : BATCH))
stmts=""
for i in $(seq 1 $current_batch); do
stmts+="{\"q\": \"INSERT INTO bench (data) VALUES (?)\", \"v\": [\"data-$inserted-$i\"]},"
done
stmts="${stmts%,}"
curl -s -XPOST "http://$HOST/db/execute" \
-H 'Content-Type: application/json' \
-d "[${stmts}]" > /dev/null
inserted=$((inserted + current_batch))
done
end=$(date +%s%N)
write_time=$(( (end - start) / 1000000 ))
write_tps=$(( TOTAL * 1000 / write_time ))
echo " 写入 $TOTAL 条: ${write_time}ms (${write_tps} ops/s)"
# 查询测试(不同一致性级别)
for level in none weak strong; do
start=$(date +%s%N)
for i in $(seq 1 100); do
curl -s -G "http://$HOST/db/query" \
--data-urlencode "q=SELECT COUNT(*) FROM bench" \
--data-urlencode "level=$level" > /dev/null
done
end=$(date +%s%N)
read_time=$(( (end - start) / 1000000 ))
read_tps=$(( 100 * 1000 / read_time ))
echo " 查询(100次,$level): ${read_time}ms (${read_tps} ops/s)"
done
# 清理
curl -s -XPOST "http://$HOST/db/execute" \
-H 'Content-Type: application/json' \
-d '[["DROP TABLE bench"]]' > /dev/null
echo ""
echo "测试完成"
9.9 优化检查清单
| 优化项 | 检查内容 | 优先级 |
|---|---|---|
| 批量写入 | 每次请求包含多条语句 | ⭐⭐⭐ |
| 索引优化 | 高频查询字段有索引 | ⭐⭐⭐ |
| 连接复用 | 使用 HTTP 连接池 | ⭐⭐⭐ |
| 一致性级别 | 读请求使用合适的 level | ⭐⭐ |
| 读写分流 | 读请求分发到 Follower | ⭐⭐ |
| 数据归档 | 定期清理旧数据 | ⭐⭐ |
| VACUUM | 定期执行 VACUUM | ⭐ |
9.10 本章小结
| 要点 | 内容 |
|---|---|
| 批量写入 | 最有效的性能优化手段,推荐 50-100 条/批 |
| 索引 | 高频查询字段必须建索引 |
| 一致性 | 根据业务需求选择最低一致级别 |
| 读写分流 | Follower 分担读取压力 |
| 连接管理 | 使用 HTTP 连接池复用连接 |
| 定期维护 | VACUUM + 数据归档 |
上一章:第 8 章:安全配置 下一章:第 10 章:客户端开发