3. 数据模型设计:批次追溯表结构设计、价格快照表设计、索引策略

在供应商交付批次追溯场景中,数据模型的设计直接决定了价格偏差预防的实时性和准确性。本章围绕批次追溯表与价格快照表两张核心表,从表结构、字段约束到索引策略进行系统性设计,确保在千万级数据量下仍能保持毫秒级查询响应。

3.1 批次追溯表结构设计

批次追溯表(batch_trace)用于记录每一批物料的完整流转链路,从供应商发货、入库、质检到生产消耗。设计原则是扁平化存储 + 冗余关键维度,避免在追溯时产生大量关联查询。

字段名 类型 约束 说明
trace_id BIGINT UNSIGNED PK, AUTO_INCREMENT 追溯主键
batch_no VARCHAR(64) NOT NULL, UNIQUE 供应商批次号(业务唯一)
material_code VARCHAR(32) NOT NULL 物料编码
supplier_id INT UNSIGNED NOT NULL 供应商ID
receipt_time DATETIME(3) NOT NULL 收货时间(毫秒精度)
quantity DECIMAL(18,4) NOT NULL 交付数量
unit_price DECIMAL(18,6) NOT NULL 实际结算单价
price_snapshot_id BIGINT UNSIGNED NOT NULL 关联价格快照ID
trace_status TINYINT DEFAULT 0 0=正常, 1=价格偏差, 2=待复核
create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) 记录创建时间
设计要点:
  • batch_no 使用唯一索引而非主键,保留自增主键的写入性能优势。
  • receipt_time 精确到毫秒,支持高频批次的时间范围查询。
  • price_snapshot_id 作为外键逻辑关联,避免追溯时实时计算价格。

3.2 价格快照表设计

价格快照表(price_snapshot)在每次供应商价格变更或新合同生效时生成一条记录,为批次追溯提供基准价格。该表是价格偏差判定的唯一依据,必须保证历史数据不可篡改。

字段名 类型 约束 说明
snapshot_id BIGINT UNSIGNED PK, AUTO_INCREMENT 快照主键
material_code VARCHAR(32) NOT NULL 物料编码
supplier_id INT UNSIGNED NOT NULL 供应商ID
effective_date DATE NOT NULL 价格生效日期
expiry_date DATE DEFAULT '9999-12-31' 价格失效日期
standard_price DECIMAL(18,6) NOT NULL 标准单价
contract_no VARCHAR(32) NOT NULL 关联合同号
snapshot_hash CHAR(64) NOT NULL SHA-256防篡改校验
create_time DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) 快照生成时间
快照防篡改机制:
snapshot_hash 字段存储对整行关键字段(material_code + supplier_id + effective_date + standard_price)的 SHA-256 哈希值。每次追溯时校验哈希,确保历史价格未被后台修改。

3.3 索引策略:聚簇索引与覆盖索引

索引设计是性能调优的核心。针对批次追溯和价格快照的查询模式,采用聚簇索引优化范围扫描,使用覆盖索引消除回表开销。

3.3.1 聚簇索引设计

InnoDB 引擎中,聚簇索引即主键索引,数据行直接存储在索引的叶子节点。对于批次追溯表,主键 trace_id 自增保证了写入的顺序性,避免页分裂。但业务查询通常按 batch_no 或 receipt_time 过滤,因此需要额外设计二级聚簇索引。

-- 批次追溯表:按收货时间聚簇,支持时间范围追溯
ALTER TABLE batch_trace ADD INDEX idx_receipt_time (receipt_time);

-- 价格快照表:按物料+供应商+有效期聚簇,快速定位生效价格
ALTER TABLE price_snapshot ADD INDEX idx_material_supplier_effective 
    (material_code, supplier_id, effective_date);
注意:不要对 batch_no 建立聚簇索引。虽然业务上唯一,但批次号是字符串类型,自增主键的写入性能远优于 UUID 或字符串聚簇。

3.3.2 覆盖索引设计

覆盖索引是指查询所需的所有字段都包含在索引中,无需回表访问数据行。在价格偏差预防场景中,以下两个查询最为频繁:

  • 查询批次价格与标准价格对比:需要 batch_trace.unit_price 和 price_snapshot.standard_price。
  • 按供应商+物料统计偏差批次:需要 trace_status、quantity、unit_price。
-- 覆盖索引:批次追溯表,避免回表获取价格和状态
ALTER TABLE batch_trace ADD INDEX idx_cover_price_check 
    (material_code, supplier_id, receipt_time, unit_price, trace_status);

-- 覆盖索引:价格快照表,快速获取标准价格
ALTER TABLE price_snapshot ADD INDEX idx_cover_standard_price 
    (material_code, supplier_id, effective_date, standard_price);
覆盖索引效果验证:
使用 EXPLAIN 查看执行计划,当 Extra 列出现 Using index 时,表示查询完全由索引覆盖,无需回表。在千万级数据量下,覆盖索引可将查询延迟从 200ms 降至 5ms 以内。

3.4 数据模型关系与查询路径

下图展示了批次追溯表与价格快照表之间的关联关系,以及典型的价格偏差判定查询路径。

批次追溯与价格快照数据模型关系图 批次追溯与价格快照数据模型关系 batch_trace(批次追溯表) trace_id (PK, 自增) batch_no (UK, 供应商批次号) material_code + supplier_id receipt_time (毫秒精度) unit_price + trace_status price_snapshot_id (FK) price_snapshot(价格快照表) snapshot_id (PK, 自增) material_code + supplier_id effective_date ~ expiry_date standard_price (标准单价) contract_no + snapshot_hash create_time price_snapshot_id 典型价格偏差判定查询路径 1. 按 batch_no 或 receipt_time 定位批次记录 2. 通过 price_snapshot_id 关联快照表 3. 对比 unit_price 与 standard_price,判定偏差

3.5 索引策略对比与选择建议

不同索引策略在写入性能、查询性能和存储开销上各有优劣。下表总结了三种核心索引策略的适用场景:

索引类型 写入性能 查询性能 存储开销 推荐场景
聚簇索引(主键自增) 极高(顺序写入) 高(范围扫描) 低(无额外索引页) 批次追溯表主键、时间范围查询
二级聚簇索引 中(需维护索引树) 高(按字段聚簇) 中 价格快照表按物料+供应商+有效期查询
覆盖索引 低(索引字段多) 极高(零回表) 高(冗余存储) 高频价格对比查询、偏差统计
索引设计黄金法则:
对于写入频繁的批次追溯表,优先保证主键自增聚簇;对于查询频繁的价格快照表,优先建立覆盖索引。在性能调优时,使用 SHOW INDEX FROM table_name 检查索引基数(Cardinality),确保索引选择性大于 30%。

3.6 数据模型与索引的协同优化

下图展示了索引在查询执行计划中的协同工作方式,以及如何通过索引下推(Index Condition Pushdown, ICP)进一步减少回表次数。

索引协同优化执行流程 索引协同优化执行流程 SQL 查询入口 优化器选择索引 聚簇索引范围扫描 receipt_time BETWEEN 覆盖索引直接返回 Using index (零回表) 二级索引+回表 Using index condition 结果集返回
索引协同优化总结:
  • 聚簇索引:适合时间范围查询,如“查询某日所有批次”。
  • 覆盖索引:适合价格偏差判定,如“对比批次单价与标准价”。
  • 索引下推(ICP):当二级索引无法完全覆盖时,MySQL 会在索引层面过滤不满足条件的行,减少回表次数。使用 EXPLAIN 确认 Extra 列是否包含 Using index condition。