4. 商户数据模型设计:核心表结构与索引优化
在高并发评分场景下,数据模型是吞吐量与准确性的基石。本章围绕商户风险评分所需的五张核心表,从字段设计、分表策略到索引优化进行系统性讲解,确保模型在千万级商户、亿级流水下仍能保持毫秒级响应。
4.1 商户基础信息表(merchant_base)
该表存储商户静态属性,写入低频、读取高频,是评分引擎的“字典”。
| 字段名 | 类型 | 说明 | 索引策略 |
|---|---|---|---|
| merchant_id | BIGINT UNSIGNED | 商户唯一标识,主键 | 聚簇索引 |
| merchant_name | VARCHAR(128) | 商户名称 | — |
| industry_code | SMALLINT | 行业分类编码 | 二级索引 |
| register_date | DATE | 注册日期 | 二级索引 |
| status | TINYINT | 0-正常 1-冻结 2-注销 | 二级索引(过滤活跃商户) |
| risk_level_init | TINYINT | 初始风险等级 1-5 | — |
| extra_json | JSON | 扩展属性(法人、地址等) | 虚拟列 + 索引(按需) |
设计要点:使用
extra_json 存储非结构化扩展字段,避免频繁 DDL;对 JSON 中高频查询字段(如法人身份证号)建立虚拟列索引,兼顾灵活性与查询性能。
4.2 交易流水表(transaction_flow)
交易流水是评分计算的核心数据源,写入量极大(日均千万~亿级),需按时间维度分表。
- 分表策略:按
trans_date做 RANGE 分区,每月一张物理表(transaction_flow_202501)。 - 写入优化:使用批量 INSERT(每批 500~1000 行),关闭二级索引实时维护(先入表后建索引或使用在线 DDL)。
- 查询模式:按商户 ID + 时间范围查询,避免全表扫描。
| 字段名 | 类型 | 说明 | 索引策略 |
|---|---|---|---|
| trans_id | BIGINT UNSIGNED | 流水主键,自增 | 聚簇索引 |
| merchant_id | BIGINT UNSIGNED | 商户 ID | 联合索引前缀 |
| trans_time | DATETIME(3) | 交易时间(毫秒精度) | 联合索引第二列 |
| trans_amount | DECIMAL(18,2) | 交易金额 | — |
| trans_type | TINYINT | 1-消费 2-退款 3-充值 | — |
| trans_status | TINYINT | 0-成功 1-失败 2-风控拦截 | — |
| device_fingerprint | VARCHAR(64) | 设备指纹 | 二级索引(反欺诈查询) |
-- 推荐索引:覆盖评分引擎最频繁的查询
ALTER TABLE transaction_flow_202501 ADD INDEX idx_merchant_time (merchant_id, trans_time);
ALTER TABLE transaction_flow_202501 ADD INDEX idx_device (device_fingerprint);
4.3 风险事件表(risk_event)
记录商户触发的各类风控事件(如频繁退款、异地登录、大额异常),用于特征衍生与历史回溯。
- 写入特点:事件驱动写入,单次写入量小但并发高。
- 保留周期:事件数据保留 180 天,超期归档至冷存储(如 HBase/OSS)。
- 查询特点:按商户 ID 查询近期事件列表,用于实时评分特征计算。
| 字段名 | 类型 | 说明 | 索引策略 |
|---|---|---|---|
| event_id | BIGINT UNSIGNED | 事件主键 | 聚簇索引 |
| merchant_id | BIGINT UNSIGNED | 商户 ID | 联合索引前缀 |
| event_type | SMALLINT | 事件类型编码 | 联合索引第二列 |
| event_time | DATETIME(3) | 事件发生时间 | 联合索引第三列 |
| event_detail | VARCHAR(512) | 事件详情 JSON | — |
| risk_score_impact | DECIMAL(5,2) | 该事件对评分的扣分值 | — |
-- 核心索引:按商户查询近期事件
ALTER TABLE risk_event ADD INDEX idx_merchant_type_time (merchant_id, event_type, event_time);
4.4 黑白名单表(risk_list)
黑白名单用于快速拦截或放行,要求极低延迟(< 5ms),通常结合缓存使用。
- 存储策略:MySQL 作为持久化层 + Redis 作为缓存层(TTL 3600s)。
- 名单类型:商户级黑名单、设备级黑名单、IP 级黑名单。
- 过期机制:名单可设置过期时间,过期后自动失效。
| 字段名 | 类型 | 说明 | 索引策略 |
|---|---|---|---|
| list_id | BIGINT UNSIGNED | 主键 | 聚簇索引 |
| list_type | TINYINT | 1-黑名单 2-白名单 | 联合索引前缀 |
| target_type | TINYINT | 1-商户 2-设备 3-IP | 联合索引第二列 |
| target_value | VARCHAR(64) | 具体值(商户ID/设备指纹/IP) | 联合索引第三列 |
| expire_time | DATETIME | 过期时间,NULL 表示永久 | 二级索引(清理过期数据) |
| reason | VARCHAR(256) | 加入原因 | — |
-- 唯一索引:防止重复名单
ALTER TABLE risk_list ADD UNIQUE INDEX uk_target (list_type, target_type, target_value);
4.5 评分结果表(risk_score_result)
存储每次评分计算的输出结果,用于报表、监控与人工审核。
- 写入频率:每次评分请求写入一条记录,写入量 = 评分 QPS。
- 查询模式:按商户 ID 查询最新评分结果,或按时间范围拉取评分趋势。
- 数据保留:保留 90 天,超期归档至分析型数据库(如 ClickHouse)。
| 字段名 | 类型 | 说明 | 索引策略 |
|---|---|---|---|
| result_id | BIGINT UNSIGNED | 主键 | 聚簇索引 |
| merchant_id | BIGINT UNSIGNED | 商户 ID | 联合索引前缀 |
| score_time | DATETIME(3) | 评分时间 | 联合索引第二列 |
| final_score | DECIMAL(5,2) | 最终风险评分 0-100 | — |
| risk_level | TINYINT | 风险等级 1-5 | 二级索引 |
| score_detail_json | JSON | 各维度得分详情 | — |
| model_version | VARCHAR(32) | 评分模型版本号 | — |
-- 覆盖索引:查询商户最新评分
ALTER TABLE risk_score_result ADD INDEX idx_merchant_score_time (merchant_id, score_time DESC);
索引优化避坑:不要对
score_detail_json 建立索引;不要在事务流水表上建立超过 5 个二级索引,否则写入性能会急剧下降。优先使用联合索引覆盖查询,避免回表。
4.6 数据模型整体架构图
下图展示了五张核心表之间的关联关系与数据流向,帮助理解评分引擎如何串联各表数据。
4.7 索引优化核心原则
在高并发场景下,索引设计直接决定数据库吞吐能力。以下是必须遵守的优化原则:
- 覆盖索引优先:让索引包含查询所需的所有字段,避免回表。例如
idx_merchant_time覆盖商户 ID + 时间范围查询。 - 联合索引最左匹配:将区分度最高的字段放在最左侧。商户 ID 作为查询入口,始终放在联合索引第一位。
- 避免冗余索引:删除长期未被使用的索引,减少写入开销。可通过
performance_schema或慢查询日志分析。 - 索引下推(ICP):MySQL 5.6+ 自动启用,联合索引中无法完全匹配的查询条件会在存储引擎层过滤,减少回表行数。
- 分区裁剪:交易流水表按时间分区后,查询语句必须带上时间条件,让优化器直接裁剪无关分区。
实战案例:某支付平台将交易流水表二级索引从 7 个优化为 3 个联合索引后,写入 TPS 从 8,000 提升至 22,000,查询 P99 延迟从 120ms 降至 18ms。核心变化是删除了单字段索引
idx_trans_type 和 idx_trans_status,改用 idx_merchant_time 覆盖 90% 的评分查询。
4.8 分库分表策略
当单表数据量超过 5000 万行或写入 QPS 超过 1 万时,必须引入分库分表。推荐策略如下:
| 表名 | 分片键 | 分片策略 | 分片数量 |
|---|---|---|---|
| transaction_flow | merchant_id | 一致性哈希 | 32 库 × 32 表 |
| risk_event | merchant_id | 一致性哈希 | 16 库 × 16 表 |
| risk_score_result | merchant_id | 一致性哈希 | 16 库 × 16 表 |
| merchant_base | merchant_id | 哈希取模 | 8 库(不拆分表) |
| risk_list | target_value | 哈希取模 | 8 库(不拆分表) |
分片键选择:所有评分查询都基于
merchant_id,因此将其作为分片键可以保证同一商户的数据落在同一分片,避免跨分片 JOIN。黑白名单表按 target_value 分片,确保名单查询也能单分片完成。