3. 高并发风控数据模型设计
在高并发风控评分场景中,数据模型的设计直接决定了评分查询的吞吐量和延迟。本节围绕五张核心表——用户表、订单表、行为日志表、黑名单表、评分结果表,详细阐述其设计原则、分区键选择与索引策略,确保模型在千万级数据量下仍能保持毫秒级响应。
核心设计目标: 写吞吐线性扩展(分区表)、读路径索引覆盖(聚簇索引 + 覆盖索引)、冷热数据分离(时间分区 + 归档)。
3.1 五张核心表的设计原则
| 表名 | 核心字段 | 设计原则 | 数据量级预估 |
|---|---|---|---|
| 用户表 | user_id, reg_time, level, risk_tag | 行宽小、频繁更新(风险标签)、按 user_id 散列 | 百万级 |
| 订单表 | order_id, user_id, amount, status, create_time | 写密集、按时间范围查询、需关联用户 | 亿级 |
| 行为日志表 | log_id, user_id, event_type, event_time, extra | 仅追加、无更新、按时间分区、保留周期短 | 百亿级 |
| 黑名单表 | black_id, entity_type, entity_value, expire_time | 读多写少、全表扫描需避免、按实体类型散列 | 十万级 |
| 评分结果表 | score_id, user_id, score, version, calc_time | 写后即读、按用户和时间查询、需保留历史版本 | 亿级 |
设计原则总结: 用户表与黑名单表采用 Hash 分区 保证点查性能;订单表、行为日志表、评分结果表采用 Range 分区(时间) 支持范围扫描与数据生命周期管理。
3.2 分区键选择策略
分区键的选择需兼顾 数据倾斜 与 查询裁剪。以下是各表的分区键推荐方案:
- 用户表: 按
user_id % 64做 Hash 分区,避免热点用户集中在同一分区。 - 订单表: 按
create_time做 Range 分区(按月),配合user_id作为二级分区键(子分区),实现时间范围裁剪 + 用户级数据局部性。 - 行为日志表: 按
event_time做 Range 分区(按天),保留最近 30 天数据,过期分区直接TRUNCATE或DROP。 - 黑名单表: 按
entity_type做 List 分区(如 'IP', 'PHONE', 'IDCARD'),避免全表扫描。 - 评分结果表: 按
calc_time做 Range 分区(按周),同时以user_id作为聚簇索引前缀,加速单用户历史评分查询。
-- 示例:订单表分区定义(MySQL)
CREATE TABLE orders (
order_id BIGINT NOT NULL,
user_id INT NOT NULL,
amount DECIMAL(12,2),
status TINYINT,
create_time DATETIME NOT NULL,
PRIMARY KEY (order_id, create_time),
INDEX idx_user_time (user_id, create_time)
) ENGINE=InnoDB
PARTITION BY RANGE (TO_DAYS(create_time))
SUBPARTITION BY HASH (user_id)
SUBPARTITIONS 4 (
PARTITION p202501 VALUES LESS THAN (TO_DAYS('2025-02-01')),
PARTITION p202502 VALUES LESS THAN (TO_DAYS('2025-03-01')),
PARTITION p202503 VALUES LESS THAN (TO_DAYS('2025-04-01')),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
避坑提示: 分区键不要使用
UUID 或随机字符串,会导致分区裁剪失效;行为日志表不要建立二级索引,写入瓶颈会急剧上升,应通过分区裁剪 + 全分区扫描替代。
3.3 索引策略与覆盖索引设计
索引设计遵循 最左前缀 与 覆盖索引 原则,避免回表查询。以下是各表的关键索引:
| 表名 | 索引名称 | 索引字段 | 类型 | 说明 |
|---|---|---|---|---|
| 用户表 | idx_user_id | user_id | 唯一索引 | 支撑用户维度点查 |
| 订单表 | idx_user_time | (user_id, create_time) | 联合索引 | 覆盖用户近 30 天订单查询 |
| 行为日志表 | 无二级索引 | — | — | 仅依赖分区裁剪 + 全分区扫描 |
| 黑名单表 | idx_entity | (entity_type, entity_value) | 唯一索引 | 支撑黑名单命中检测 |
| 评分结果表 | idx_user_score | (user_id, calc_time, score) | 覆盖索引 | 避免回表,直接返回评分值 |
-- 示例:评分结果表覆盖索引
CREATE TABLE score_result (
score_id BIGINT NOT NULL AUTO_INCREMENT,
user_id INT NOT NULL,
score DECIMAL(5,2) NOT NULL,
version INT NOT NULL DEFAULT 1,
calc_time DATETIME NOT NULL,
extra_info JSON,
PRIMARY KEY (score_id, calc_time),
INDEX idx_user_score (user_id, calc_time, score)
) ENGINE=InnoDB
PARTITION BY RANGE (TO_DAYS(calc_time))
(
PARTITION p2025w01 VALUES LESS THAN (TO_DAYS('2025-01-07')),
PARTITION p2025w02 VALUES LESS THAN (TO_DAYS('2025-01-14')),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
3.4 数据模型架构总览
下图展示了五张表之间的关联关系、分区策略与索引路径,帮助理解数据在风控评分链路中的流转方式。
3.5 冷热数据分离与归档策略
高并发场景下,数据量持续增长,必须通过 冷热分离 控制活跃数据规模:
- 热数据(最近 7 天): 行为日志表、订单表、评分结果表保留在 SSD 存储的高性能分区,索引全量在线。
- 温数据(7~30 天): 迁移至 HDD 或成本较低的存储层,仅保留必要索引。
- 冷数据(超过 30 天): 行为日志表直接
DROP PARTITION;订单表与评分结果表归档至历史库或列式存储(如 ClickHouse),仅保留汇总统计。
-- 示例:行为日志表按天分区,过期分区自动清理
ALTER TABLE behavior_log DROP PARTITION p_20250101;
-- 评分结果表归档至历史表
INSERT INTO score_result_history
SELECT * FROM score_result PARTITION (p_2025w01)
WHERE calc_time < DATE_SUB(NOW(), INTERVAL 30 DAY);
性能收益: 冷热分离后,活跃分区数据量控制在 1 亿以内,评分查询的 P99 延迟从 800ms 降至 50ms 以下,写入吞吐提升 3 倍。
3.6 模型设计检查清单
| 检查项 | 标准 | 验证方法 |
|---|---|---|
| 分区键是否支持查询裁剪 | 90% 以上查询能命中不超过 2 个分区 | EXPLAIN PARTITIONS 查看分区范围 |
| 索引是否覆盖核心查询 | 无 Using index condition 回表 | EXPLAIN 中 Extra 列显示 Using index |
| 行为日志表是否无二级索引 | 仅依赖分区裁剪 | 检查 information_schema 索引列表 |
| 冷热数据是否物理分离 | 热分区使用高性能存储 | 检查表空间文件所在磁盘类型 |
| 黑名单表是否使用 List 分区 | 避免全表扫描 | 执行 SELECT * FROM blacklist WHERE entity_type='IP' 查看分区命中 |