第八章:性能优化¶
查询优化¶
主键索引¶
-- 主键选择原则
-- 1. 选择高基数列
-- 2. 选择常用过滤条件
-- 3. 选择时间列(时序数据)
CREATE TABLE events (
event_id UInt32,
event_type String,
event_time DateTime,
user_id UInt32
) ENGINE = MergeTree()
ORDER BY (event_time, event_id) -- 主键
SETTINGS index_granularity = 8192;
分区优化¶
-- 合理分区
-- 1. 按时间分区(常用)
-- 2. 分区不宜过多
-- 3. 单个分区不宜过大
CREATE TABLE events (
event_id UInt32,
event_type String,
event_time DateTime
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_time) -- 按月分区
ORDER BY (event_time, event_id);
-- 查询时利用分区裁剪
SELECT * FROM events
WHERE event_time >= '2024-01-01' AND event_time < '2024-02-01';
查询优化¶
-- 1. 使用 PREWHERE 代替 WHERE
SELECT user_id, event_type
FROM events
PREWHERE event_type = 'click' -- 先过滤再读取
WHERE event_time > now() - INTERVAL 1 DAY;
-- 2. 限制返回列
SELECT event_id, event_type FROM events; -- 好
SELECT * FROM events; -- 避免
-- 3. 使用 LIMIT
SELECT * FROM events LIMIT 100;
-- 4. 避免使用 FINAL
SELECT * FROM events; -- 好
SELECT * FROM events FINAL; -- 避免
-- 5. 使用近似函数
SELECT uniq(user_id) FROM events; -- 快
SELECT uniqExact(user_id) FROM events; -- 慢
写入优化¶
批量写入¶
-- 批量写入
INSERT INTO events FORMAT JSONEachRow
{"event_id": 1, ...}
{"event_id": 2, ...}
...
{"event_id": 10000, ...}
-- 避免小批量写入
INSERT INTO events VALUES (1, ...); -- 避免
异步写入¶
-- 开启异步写入
SET async_insert = 1;
SET async_insert_max_data_size = 100000;
SET async_insert_busy_timeout_ms = 10000;
INSERT INTO events VALUES ...;
内存优化¶
内存配置¶
<!-- config.xml -->
<clickhouse>
<!-- 最大内存使用 -->
<max_memory_usage>10000000000</max_memory_usage>
<!-- 单个查询最大内存 -->
<max_memory_usage_for_user>5000000000</max_memory_usage_for_user>
<!-- 缓存配置 -->
<mark_cache_size>5368709120</mark_cache_size>
<uncompressed_cache_size>8589934592</uncompressed_cache_size>
</clickhouse>
查询内存控制¶
-- 限制查询内存
SET max_memory_usage = 1000000000;
-- 使用外部排序
SET max_bytes_before_external_sort = 100000000;
磁盘优化¶
存储配置¶
<!-- config.xml -->
<clickhouse>
<storage_configuration>
<disks>
<default>
<path>/var/lib/clickhouse/</path>
</default>
<ssd>
<path>/mnt/ssd/clickhouse/</path>
</ssd>
<hdd>
<path>/mnt/hdd/clickhouse/</path>
</hdd>
</disks>
<policies>
<hot_cold>
<volumes>
<hot>
<disk>ssd</disk>
<max_data_part_size_bytes>1073741824</max_data_part_size_bytes>
</hot>
<cold>
<disk>hdd</disk>
</cold>
</volumes>
</hot_cold>
</policies>
</storage_configuration>
</clickhouse>
TTL 配置¶
-- 自动过期数据
CREATE TABLE events (
event_id UInt32,
event_time DateTime
) ENGINE = MergeTree()
ORDER BY event_id
TTL event_time + INTERVAL 30 DAY;
-- 自动移动到冷存储
CREATE TABLE events (
event_id UInt32,
event_time DateTime
) ENGINE = MergeTree()
ORDER BY event_id
TTL event_time + INTERVAL 7 DAY TO VOLUME 'cold';
监控指标¶
关键指标¶
| 指标 | 说明 |
|---|---|
| Query | 查询数量 |
| Insert | 插入数量 |
| Merge | 合并数量 |
| Part | 分区数量 |
| Memory | 内存使用 |
| CPU | CPU 使用 |
| Disk | 磁盘使用 |
系统表¶
-- 查询日志
SELECT * FROM system.query_log
WHERE event_date = today()
ORDER BY query_duration_ms DESC
LIMIT 10;
-- 合并状态
SELECT * FROM system.merges;
-- 分区状态
SELECT
database,
table,
count() as part_count,
sum(rows) as total_rows,
sum(bytes_on_disk) as total_bytes
FROM system.parts
WHERE active = 1
GROUP BY database, table;
最佳实践¶
1. 表设计¶
- 选择合适的主键
- 合理分区
- 使用合适的引擎
2. 查询¶
- 使用 PREWHERE
- 限制返回列
- 使用近似函数
3. 写入¶
- 批量写入
- 避免频繁小写入
- 使用异步写入
4. 监控¶
- 监控关键指标
- 定期清理过期数据
- 优化大查询
小结¶
性能优化要点:
- 查询优化:主键、分区、PREWHERE
- 写入优化:批量写入、异步写入
- 内存优化:内存配置、查询控制
- 磁盘优化:存储配置、TTL
完成本教程后,你应该能够在生产环境中部署和优化 ClickHouse 数据库。