跳转至

第八章:性能优化

查询优化

主键索引

-- 主键选择原则
-- 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 数据库。