4. 交易流水表结构设计:字段定义与索引原则
交易流水表是实时风控系统的核心数据载体,每一笔支付、转账、提现请求都会先写入该表,再由风控引擎进行规则判定。表结构设计的优劣直接影响 SQL 巡检效率、告警阈值计算的准确性以及系统扩展性。本节从字段定义和索引设计两个维度展开,给出生产级建表规范。
4.1 核心字段定义
交易流水表至少包含以下 6 类关键字段,每类字段在风控场景中承担特定职责:
| 字段名 | 类型示例 | 风控用途 | 约束说明 |
|---|---|---|---|
| 交易 ID | VARCHAR(64) |
全局唯一标识,用于去重、关联、幂等 | 主键,建议使用雪花算法或 UUID 短码 |
| 时间戳 | DATETIME(3) |
记录交易发生时刻,用于窗口聚合、时序分析 | 必须精确到毫秒,建索引;禁止使用字符串 |
| 金额 | DECIMAL(18,2) |
阈值判定、累计求和、异常波动检测 | 单位统一为元;禁止使用 FLOAT/DOUBLE |
| 商户号 | VARCHAR(32) |
商户维度风控、分账、限额管理 | 非空,高频查询字段,需建索引 |
| 用户 ID | VARCHAR(64) |
用户画像、行为画像、黑名单匹配 | 非空,与商户号组成联合索引 |
| 状态码 | TINYINT |
标识交易结果(成功/失败/风控拒绝/超时) | 枚举值,用于过滤和统计 |
生产避坑:时间戳字段不要使用
VARCHAR 或 BIGINT 存储,否则会导致时间函数无法使用索引,巡检 SQL 性能急剧下降。金额字段必须使用定点数 DECIMAL,避免浮点精度误差引发阈值误判。
4.2 扩展字段与状态码枚举
除核心字段外,生产环境通常还会增加以下扩展字段以支撑更精细的风控逻辑:
- 设备指纹(
VARCHAR(128)):用于识别设备关联风险。 - IP 地址(
VARCHAR(45)):支持 IPv4/IPv6,用于异地登录检测。 - 渠道编码(
VARCHAR(16)):区分 APP、H5、PC、API 等来源。 - 风控标签(
VARCHAR(64)):实时规则命中后写入,用于事后分析。
状态码建议使用 TINYINT 枚举,典型映射如下:
| 状态码 | 含义 | 是否计入告警阈值 |
|---|---|---|
| 0 | 待风控审核 | 否 |
| 1 | 交易成功 | 否 |
| 2 | 交易失败(余额不足等) | 是 |
| 3 | 风控拒绝 | 是 |
| 4 | 超时未响应 | 是 |
4.3 索引设计原则
实时风控巡检 SQL 通常涉及 时间范围扫描、商户/用户分组聚合、状态过滤 三类操作。索引设计必须围绕这些访问模式展开,遵循以下 5 条核心原则:
- 时间戳前缀索引:所有巡检 SQL 几乎都带时间窗口条件,
tx_time必须作为索引最左列。 - 等值条件前置:商户号、用户 ID、状态码等等值过滤字段放在联合索引左侧,时间戳放在右侧。
- 覆盖索引优先:对于高频聚合查询(如统计某商户每分钟交易量),建立包含所有查询字段的联合索引,避免回表。
- 避免冗余索引:不要为每个字段单独建索引,优先使用联合索引覆盖多个查询模式。
- 索引下推利用:MySQL 5.6+ 支持索引下推,联合索引中后置字段的过滤可以在索引层完成,减少回表次数。
巡检场景示例:“查询商户 M001 在最近 5 分钟内状态码为 3(风控拒绝)的交易量” —— 联合索引应设计为
(merchant_id, status_code, tx_time),其中 tx_time 用于范围过滤。
4.4 索引设计对比:好与坏
| 设计方式 | 索引定义 | 适用场景 | 性能表现 |
|---|---|---|---|
| 推荐 | INDEX idx_merchant_status_time (merchant_id, status_code, tx_time) |
按商户+状态+时间范围查询 | 索引精准过滤,回表极少 |
| 不推荐 | INDEX idx_time (tx_time) + INDEX idx_merchant (merchant_id) |
单字段独立查询 | 无法利用索引合并,回表多 |
| 不推荐 | INDEX idx_time_merchant (tx_time, merchant_id) |
时间范围+商户等值 | 时间列选择性差,索引膨胀快 |
4.5 建表 DDL 示例
以下为生产级交易流水表 DDL,包含核心字段、扩展字段和推荐索引:
CREATE TABLE txn_flow (
txn_id VARCHAR(64) NOT NULL COMMENT '交易ID',
tx_time DATETIME(3) NOT NULL COMMENT '交易时间戳(毫秒)',
amount DECIMAL(18,2) NOT NULL COMMENT '交易金额(元)',
merchant_id VARCHAR(32) NOT NULL COMMENT '商户号',
user_id VARCHAR(64) NOT NULL COMMENT '用户ID',
status_code TINYINT NOT NULL COMMENT '状态码:0待审/1成功/2失败/3风控拒/4超时',
device_fp VARCHAR(128) DEFAULT NULL COMMENT '设备指纹',
client_ip VARCHAR(45) DEFAULT NULL COMMENT '客户端IP',
channel_code VARCHAR(16) DEFAULT NULL COMMENT '渠道编码',
risk_tag VARCHAR(64) DEFAULT NULL COMMENT '风控标签',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
PRIMARY KEY (txn_id),
INDEX idx_merchant_status_time (merchant_id, status_code, tx_time),
INDEX idx_user_time (user_id, tx_time),
INDEX idx_time (tx_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交易流水表';
注意:不要在主键上使用自增 ID,交易 ID 应由业务系统生成全局唯一 ID,避免分库分表时主键冲突。时间戳索引
idx_time 仅用于纯时间范围扫描的后备场景,日常巡检应优先使用联合索引。
4.6 交易流水表结构总览
下图展示了交易流水表的字段分层、索引策略与风控巡检 SQL 的对应关系:
4.7 索引设计常见误区
- 误区一:为每个字段单独建索引,认为“索引越多查询越快”。实际会导致优化器选择困难,写入性能下降,磁盘空间膨胀。
- 误区二:将时间戳放在联合索引最左侧。对于“商户+状态+时间”查询,时间戳选择性高但范围大,放在最左会导致后续字段无法利用索引。
- 误区三:使用
SELECT *配合覆盖索引。如果查询字段超出索引范围,MySQL 必须回表,覆盖索引失效。 - 误区四:忽略状态码过滤。状态码虽然只有几个枚举值,但在联合索引中作为等值条件可以大幅减少扫描行数。
最佳实践:在开发环境使用
EXPLAIN 分析巡检 SQL 的执行计划,重点关注 type(至少达到 ref 或 range)、key_len(索引使用长度)和 Extra(是否出现 Using index)。对于高频巡检 SQL,建议将查询字段和过滤字段全部纳入联合索引,实现覆盖索引扫描。