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 条核心原则:

  1. 时间戳前缀索引:所有巡检 SQL 几乎都带时间窗口条件,tx_time 必须作为索引最左列。
  2. 等值条件前置:商户号、用户 ID、状态码等等值过滤字段放在联合索引左侧,时间戳放在右侧。
  3. 覆盖索引优先:对于高频聚合查询(如统计某商户每分钟交易量),建立包含所有查询字段的联合索引,避免回表。
  4. 避免冗余索引:不要为每个字段单独建索引,优先使用联合索引覆盖多个查询模式。
  5. 索引下推利用: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 的对应关系:

交易流水表结构总览 核心字段层 • 交易ID (PK) • 时间戳 (DATETIME(3)) • 金额 (DECIMAL) • 商户号 • 用户ID • 状态码 (TINYINT) 扩展字段层 • 设备指纹 • 客户端IP • 渠道编码 • 风控标签 • 创建时间 索引策略层 • PK: txn_id • idx_merchant_status_time • idx_user_time • idx_time (后备) 巡检 SQL 访问模式 ① 时间窗口扫描:WHERE tx_time BETWEEN ? AND ? ② 商户+状态过滤:WHERE merchant_id=? AND status_code=? AND tx_time BETWEEN ? AND ? 索引设计 5 原则 1. 时间戳前缀索引 2. 等值条件前置 3. 覆盖索引优先 4. 避免冗余索引 5. 利用索引下推

4.7 索引设计常见误区

  • 误区一:为每个字段单独建索引,认为“索引越多查询越快”。实际会导致优化器选择困难,写入性能下降,磁盘空间膨胀。
  • 误区二:将时间戳放在联合索引最左侧。对于“商户+状态+时间”查询,时间戳选择性高但范围大,放在最左会导致后续字段无法利用索引。
  • 误区三:使用 SELECT * 配合覆盖索引。如果查询字段超出索引范围,MySQL 必须回表,覆盖索引失效。
  • 误区四:忽略状态码过滤。状态码虽然只有几个枚举值,但在联合索引中作为等值条件可以大幅减少扫描行数。
最佳实践:在开发环境使用 EXPLAIN 分析巡检 SQL 的执行计划,重点关注 type(至少达到 ref 或 range)、key_len(索引使用长度)和 Extra(是否出现 Using index)。对于高频巡检 SQL,建议将查询字段和过滤字段全部纳入联合索引,实现覆盖索引扫描。