性能优化 #
一、性能优化概述 #
1.1 优化方向 #
text
性能优化方向
┌─────────────────────────────────────────────────────────────┐
│ │
│ 查询优化: │
│ ├── SQL语句优化 │
│ ├── 索引使用 │
│ └── 执行计划分析 │
│ │
│ Schema优化: │
│ ├── 表结构设计 │
│ ├── 索引设计 │
│ └── 分区设计 │
│ │
│ 配置优化: │
│ ├── 内存配置 │
│ ├── 并发配置 │
│ └── 存储配置 │
│ │
│ 架构优化: │
│ ├── 节点数量 │
│ ├── 数据分布 │
│ └── 多区域部署 │
│ │
└─────────────────────────────────────────────────────────────┘
二、查询优化 #
2.1 执行计划分析 #
sql
-- 查看执行计划
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
-- 查看详细执行计划
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';
-- 执行计划输出解读
EXPLAIN SELECT u.name, o.order_no
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.email = 'test@example.com';
-- 输出示例:
-- • hash join
-- │
-- ├── • scan
-- │ table: users@idx_email
-- │ spans: [/'test@example.com' - /'test@example.com']
-- │
-- └── • scan
-- table: orders@idx_user_id
2.2 SQL优化技巧 #
sql
-- 1. 避免 SELECT *
-- ✗ 不推荐
SELECT * FROM users;
-- ✓ 推荐
SELECT id, name, email FROM users;
-- 2. 使用索引列过滤
-- 确保索引列在 WHERE 条件中
SELECT * FROM users WHERE email = 'test@example.com';
-- 3. 避免函数导致索引失效
-- ✗ 不推荐
SELECT * FROM users WHERE UPPER(email) = 'TEST@EXAMPLE.COM';
-- ✓ 推荐
SELECT * FROM users WHERE email = 'test@example.com';
-- 4. 使用覆盖索引
CREATE INDEX idx_user_email_name ON users(email, name);
SELECT email, name FROM users WHERE email = 'test@example.com';
-- 5. 批量操作
-- ✗ 不推荐
INSERT INTO users (name) VALUES ('a');
INSERT INTO users (name) VALUES ('b');
INSERT INTO users (name) VALUES ('c');
-- ✓ 推荐
INSERT INTO users (name) VALUES ('a'), ('b'), ('c');
-- 6. 使用 LIMIT
SELECT * FROM users LIMIT 100;
-- 7. 避免 OR
-- ✗ 不推荐
SELECT * FROM orders WHERE user_id = 1 OR amount > 100;
-- ✓ 推荐
SELECT * FROM orders WHERE user_id = 1
UNION
SELECT * FROM orders WHERE amount > 100;
2.3 JOIN优化 #
sql
-- 1. 确保连接列有索引
CREATE INDEX idx_orders_user_id ON orders(user_id);
-- 2. 小表驱动大表
-- 让结果集小的表作为驱动表
-- 3. 使用覆盖索引减少回表
CREATE INDEX idx_user_order ON orders(user_id, order_no, amount);
SELECT user_id, order_no, amount FROM orders WHERE user_id = 1;
-- 4. 避免笛卡尔积
-- 确保所有表都有连接条件
三、索引优化 #
3.1 索引选择 #
sql
-- 1. 选择性高的列适合索引
-- 唯一值多的列
CREATE INDEX idx_email ON users(email);
-- 2. 复合索引顺序
-- 等值条件在前,范围条件在后
CREATE INDEX idx_user_status_created ON orders(user_id, status, created_at);
-- 3. 覆盖索引
-- 索引包含所有查询列
CREATE INDEX idx_covering ON orders(user_id, status, amount);
SELECT user_id, status, amount FROM orders WHERE user_id = 1;
-- 4. 部分索引
-- 只索引需要的行
CREATE INDEX idx_active ON users(email) WHERE status = 'active';
3.2 索引监控 #
sql
-- 查看索引使用情况
SELECT
schemaname,
tablename,
indexname,
idx_scan,
idx_tup_read,
idx_tup_fetch
FROM pg_stat_user_indexes
ORDER BY idx_scan DESC;
-- 查找未使用的索引
SELECT
schemaname,
tablename,
indexname
FROM pg_stat_user_indexes
WHERE idx_scan = 0
AND indexname NOT LIKE '%pkey%';
-- 查看索引大小
SELECT
schemaname,
tablename,
indexname,
pg_size_pretty(pg_relation_size(indexrelid)) AS size
FROM pg_stat_user_indexes
ORDER BY pg_relation_size(indexrelid) DESC;
四、Schema优化 #
4.1 表设计 #
sql
-- 1. 选择合适的主键
-- 推荐: UUID
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100)
);
-- 不推荐: 自增ID (热点问题)
CREATE TABLE users_bad (
id BIGINT PRIMARY KEY DEFAULT unique_rowid(),
name VARCHAR(100)
);
-- 2. 选择合适的数据类型
-- 使用最小的满足需求的类型
CREATE TABLE products (
id UUID PRIMARY KEY,
name VARCHAR(100), -- 不是 TEXT
price DECIMAL(10, 2), -- 不是 FLOAT
stock INT, -- 不是 BIGINT
status SMALLINT -- 状态码用 SMALLINT
);
-- 3. 避免过多列
-- 宽表性能差,考虑垂直拆分
-- 4. 使用分区
CREATE TABLE logs (
id UUID PRIMARY KEY,
message TEXT,
created_at TIMESTAMP
) PARTITION BY RANGE (created_at);
4.2 反范式设计 #
sql
-- 适当反范式,减少 JOIN
-- 范式设计
CREATE TABLE orders (
id UUID PRIMARY KEY,
user_id UUID,
amount DECIMAL(10, 2),
created_at TIMESTAMP
);
-- 反范式设计 (冗余用户名)
CREATE TABLE orders (
id UUID PRIMARY KEY,
user_id UUID,
user_name VARCHAR(100), -- 冗余字段
amount DECIMAL(10, 2),
created_at TIMESTAMP
);
-- 查询时不需要 JOIN
SELECT id, user_name, amount FROM orders WHERE user_id = 1;
五、配置优化 #
5.1 内存配置 #
bash
# 启动参数
cockroach start \
--cache=8GB \ # 缓存大小 (总内存的 50%)
--max-sql-memory=4GB # SQL内存 (总内存的 25%)
5.2 集群设置 #
sql
-- 查看集群设置
SHOW ALL CLUSTER SETTINGS;
-- 调整设置
SET CLUSTER SETTING sql.defaults.distsql = 'on';
SET CLUSTER SETTING kv.raft_log.disable_synchronization_unsafe = 'false';
-- 并行查询
SET CLUSTER SETTING sql.distsql.temp_storage.workmem = '512MiB';
5.3 Zone配置 #
sql
-- 调整Range大小
ALTER RANGE default CONFIGURE ZONE USING
range_min_bytes = 134217728, -- 128MB
range_max_bytes = 536870912; -- 512MB
-- 调整副本数
ALTER DATABASE mydb CONFIGURE ZONE USING
num_replicas = 5;
-- 调整GC时间
ALTER DATABASE mydb CONFIGURE ZONE USING
gc.ttlseconds = 3600; -- 1小时
六、监控与诊断 #
6.1 性能监控 #
sql
-- 查看慢查询
SELECT
query,
count,
mean_latency,
max_latency
FROM crdb_internal.node_statement_statistics
ORDER BY mean_latency DESC
LIMIT 10;
-- 查看事务统计
SELECT * FROM crdb_internal.cluster_transactions;
-- 查看节点状态
SELECT * FROM crdb_internal.kv_node_status;
-- 查看Range统计
SELECT
range_id,
round(statistics->>'key_bytes'::numeric / 1024 / 1024, 2) AS size_mb
FROM crdb_internal.ranges
ORDER BY size_mb DESC
LIMIT 10;
6.2 问题诊断 #
sql
-- 查看锁等待
SELECT * FROM crdb_internal.cluster_locks;
-- 查看阻塞的事务
SELECT
id,
status,
num_retries,
last_error
FROM crdb_internal.cluster_transactions
WHERE num_retries > 0;
-- 查看热点Range
SELECT
range_id,
lease_holder,
ROUND(statistics->>'writes_per_second'::numeric, 2) AS writes_per_sec
FROM crdb_internal.ranges
ORDER BY writes_per_sec DESC
LIMIT 10;
七、最佳实践 #
7.1 优化清单 #
text
性能优化清单
┌─────────────────────────────────────────────────────────────┐
│ │
│ Schema设计: │
│ □ 使用UUID主键 │
│ □ 选择合适的数据类型 │
│ □ 适当反范式 │
│ □ 使用分区 │
│ │
│ 索引优化: │
│ □ 为查询条件创建索引 │
│ □ 使用复合索引 │
│ □ 使用覆盖索引 │
│ □ 定期检查未使用索引 │
│ │
│ 查询优化: │
│ □ 避免 SELECT * │
│ □ 使用批量操作 │
│ □ 使用 LIMIT │
│ □ 分析执行计划 │
│ │
│ 配置优化: │
│ □ 合理配置内存 │
│ □ 调整Zone设置 │
│ □ 监控集群状态 │
│ │
└─────────────────────────────────────────────────────────────┘
八、总结 #
性能优化要点:
| 方向 | 说明 |
|---|---|
| 查询优化 | SQL优化、执行计划分析 |
| 索引优化 | 索引设计、监控 |
| Schema优化 | 表设计、分区 |
| 配置优化 | 内存、Zone设置 |
| 监控诊断 | 慢查询、热点分析 |
下一步,让我们学习监控告警!
最后更新:2026-03-27