返回数据与分布式测试模块
Data Systems / Tutorial 10

SQL 与数据库测试教程

从“页面显示下单成功”继续向后检查,用数据证明订单、金额、库存和优惠券真的正确。

10 个章节商城下单数据链路SQL + 图解 + 可执行校验
01

从页面结果追到数据库

结果要有数据证据

下单成功不等于数据一定正确

页面出现“下单成功”后,继续确认四件事:订单只创建了一次、商品金额计算正确、库存扣减正确、优惠券状态与订单结果一致。SQL 能帮助你找到这些结果在数据库里的真实记录。

一次下单会留下四层证据
01页面

提交订单

02接口

返回订单号

03服务

执行规则

04数据库

保存业务结果

页面证据

成功提示、订单号、实付金额和订单状态符合用户看到的结果。

接口证据

响应码、业务码、订单 ID 和金额字段符合接口契约。

数据库证据

订单、明细、库存和优惠记录之间能够互相对应。

数据库测试不是看到一行记录就结束,而是检查一次业务操作影响的所有关键数据是否完整、一致、可追溯。
02

看懂表关系并准备测试数据

先知道数据在哪里
订单是核心,商品、库存和优惠数据通过主键与外键连接
Aproducts

sku_id → 商品与库存

Borders + order_items

order_id → 订单与明细

Ccoupon_records

coupon_id + order_id → 使用记录

下单链路中的核心数据表

数据表保存什么重点字段
orders订单主表order_id、user_id、status、total_amount、pay_amount
order_items订单商品明细order_id、sku_id、quantity、unit_price
products商品与库存sku_id、stock、sale_price、version
coupons优惠券coupon_id、threshold_amount、discount_amount、status
coupon_records用户领券与使用记录user_id、coupon_id、order_id、use_status
准备数据时,同时记录操作前基线和可追踪标识
01建立基线

记录库存、券状态和已有订单数

02执行操作

使用测试账号与 test_run_id 下单

03对比变化

只核对本次操作产生的差异

按场景准备一组可复用数据

测试场景准备的数据预期结果
正常下单库存 10;购买 2 件;商品单价 60 元订单创建、库存扣 2、满减后实付 100 元
库存边界库存 2;购买 2 件可以创建订单,库存变为 0
库存不足库存 1;购买 2 件不创建订单,库存仍为 1
优惠边界商品总额正好 100 元满 100 减 20 生效,实付 80 元
重复提交相同幂等键连续提交两次只存在一笔订单,只扣一次库存

造数前先确认环境边界

  • 优先使用专用测试环境和测试账号,不使用真实用户数据。
  • 能通过页面或接口准备的数据,优先走真实业务入口。
  • 必须写库造数时,先确认表约束、关联数据和回收方案。
  • 生产环境默认只做经过授权的只读查询,禁止直接修改业务数据。
03

用 SELECT 和 WHERE 找到目标订单

先缩小查询范围
WHERE 条件逐步收窄,直到找到唯一业务记录
全表风险高、结果多
时间范围限定测试窗口
测试账号排除其他用户
订单号定位唯一结果

根据订单号查询一笔订单

SELECT
  order_id,
  user_id,
  status,
  total_amount,
  discount_amount,
  pay_amount,
  created_at
FROM orders
WHERE order_id = 'ORDER_202608090001';

查询用户最近创建的订单

SELECT order_id, status, pay_amount, created_at
FROM orders
WHERE user_id = 10086
  AND created_at >= '2026-08-09 10:00:00'
  AND created_at <  '2026-08-09 11:00:00'
ORDER BY created_at DESC
LIMIT 20;

查询条件要具体

  • 优先使用订单号、用户 ID、幂等键等精确标识。
  • 时间范围同时写开始和结束,避免查到其他测试数据。
  • 明确排序和数量限制,防止结果顺序不稳定。

不要用 SELECT *

  • 只查询本次要验证的字段。
  • 降低误读敏感字段和大字段的风险。
  • 字段变化时,校验脚本更容易发现影响。
04

用 JOIN 核对订单与商品明细

关联数据不能断
JOIN 使用关联键,把分散的数据重新拼成一笔完整订单
主表orders

order_id = ORDER_001

order_id
明细order_items

SKU、数量、单价

sku_id
商品products

库存与商品信息

先根据验证目的选择 JOIN

方式保留的数据下单测试用途
INNER JOIN只保留两边都匹配的数据找出订单与明细完整匹配的记录
LEFT JOIN保留左表全部数据发现没有商品明细的异常订单
多表 JOIN沿业务外键继续关联同时核对订单、商品、优惠券和用户

查询订单及其商品明细

SELECT
  o.order_id,
  o.status,
  i.sku_id,
  i.quantity,
  i.unit_price,
  i.quantity * i.unit_price AS line_amount
FROM orders AS o
JOIN order_items AS i
  ON i.order_id = o.order_id
WHERE o.order_id = 'ORDER_202608090001';

找出没有商品明细的异常订单

SELECT o.order_id, o.status, o.created_at
FROM orders AS o
LEFT JOIN order_items AS i
  ON i.order_id = o.order_id
WHERE i.order_id IS NULL
  AND o.created_at >= '2026-08-09 00:00:00';
JOIN 后行数变多不一定是重复数据。一个订单包含多件商品时,订单主表的一行会对应明细表的多行。先理解一对一还是一对多,再判断结果是否异常。
05

用 GROUP BY 核对数量与汇总金额

从明细算回总数
GROUP BY 把多行明细按订单聚合成可核对的结果
明细行SKU-A × 2 = 120
SKU-B × 1 = 40
SUM + GROUP BY
订单汇总商品数量 = 3
原始金额 = 160

按订单汇总商品数量和原始金额

SELECT
  order_id,
  SUM(quantity) AS total_quantity,
  SUM(quantity * unit_price) AS calculated_total
FROM order_items
WHERE order_id = 'ORDER_202608090001'
GROUP BY order_id;

检查是否产生重复订单

SELECT
  idempotency_key,
  COUNT(*) AS order_count
FROM orders
WHERE user_id = 10086
  AND created_at >= '2026-08-09 10:00:00'
GROUP BY idempotency_key
HAVING COUNT(*) > 1;

WHERE 先过滤

WHERE 在分组前过滤原始行,例如限定本轮测试订单和时间范围。

HAVING 再筛选

HAVING 在分组后过滤汇总结果,例如只保留出现两次以上的幂等键。

06

重新计算订单金额

不要只相信结果字段
金额不是孤立字段,而是一条可以重新计算的等式
商品明细Σ 数量 × 单价
优惠金额满足门槛后扣减
实付金额原始金额 - 优惠

原始金额 - 优惠金额 = 实付金额

用商品明细重新计算并与订单主表对比

SELECT
  o.order_id,
  o.total_amount AS stored_total,
  SUM(i.quantity * i.unit_price) AS calculated_total,
  o.discount_amount,
  o.pay_amount,
  SUM(i.quantity * i.unit_price)
    - o.discount_amount AS calculated_pay,
  o.pay_amount - (
    SUM(i.quantity * i.unit_price) - o.discount_amount
  ) AS difference
FROM orders AS o
JOIN order_items AS i
  ON i.order_id = o.order_id
WHERE o.order_id = 'ORDER_202608090001'
GROUP BY o.order_id, o.total_amount,
  o.discount_amount, o.pay_amount;

金额校验需要回答的问题

  • 明细金额之和是否等于订单原始金额。
  • 优惠门槛使用优惠前还是优惠后的金额判断。
  • 实付金额是否等于原始金额减去优惠。
  • 金额字段的精度、舍入方式和币种是否一致。
  • 差值是否严格为 0,还是允许明确的精度误差。
金融和交易字段不要用浮点数想当然地比较。先确认数据库字段类型、计算精度和舍入规则,再决定允许的差异范围。
07

核对库存和跨表数据一致性

失败时也要保持原状
同一个业务结果要在多张表中保持一致
01订单

状态与金额

02明细

商品与数量

03库存

成功才扣减

04优惠券

使用状态同步

一次下单需要同时成立的关系

检查对象实际数据对照数据通过标准
订单金额orders.total_amountSUM(order_items.quantity × unit_price)金额完全相等
库存扣减products.stock下单前库存 - 成功购买数量失败订单不影响库存
优惠券状态coupon_records.use_status订单是否成功创建成功时 USED,失败时 AVAILABLE
订单状态orders.status支付或取消事件状态变化符合业务流转

查询商品当前库存

SELECT sku_id, stock, version, updated_at
FROM products
WHERE sku_id = 'SKU_10001';

核对本轮成功订单扣减的商品数量

SELECT
  i.sku_id,
  SUM(i.quantity) AS sold_quantity
FROM order_items AS i
JOIN orders AS o
  ON o.order_id = i.order_id
WHERE i.sku_id = 'SKU_10001'
  AND o.status IN ('PENDING_PAYMENT', 'PAID', 'COMPLETED')
  AND o.created_at >= '2026-08-09 10:00:00'
  AND o.created_at <  '2026-08-09 11:00:00'
GROUP BY i.sku_id;
库存核对需要记录操作前的基线。没有“下单前库存”,只看下单后的一个数字,无法证明本次操作到底扣了多少。
08

验证事务、回滚和并发下单

要么一起成功,要么一起失败
一次下单的关键变化应该处于同一个受控事务中
01BEGIN

开始事务

02订单

写主表与明细

03库存

条件扣减

04优惠券

标记已使用

05COMMIT

全部成功后提交

任一步失败 → ROLLBACK → 订单、库存和优惠券都恢复到操作前

成功事务

  • 订单主表和商品明细同时写入。
  • 库存按购买数量扣减。
  • 优惠券标记为已使用并绑定订单。
  • 所有变化属于同一次明确的业务结果。

失败回滚

  • 订单写入失败时不扣库存。
  • 库存扣减失败时不留下半成品订单。
  • 优惠券使用失败时不改变订单金额。
  • 超时重试不会重复执行已成功的动作。

并发验证:两个请求同时购买最后一件商品

-- 请求完成后执行只读核对
SELECT sku_id, stock
FROM products
WHERE sku_id = 'SKU_LAST_ONE';

SELECT order_id, user_id, status
FROM orders
WHERE test_run_id = 'RUN_20260809_CONCURRENT'
ORDER BY created_at;

-- 预期:库存为 0,只有一个请求成功创建有效订单

数据库操作安全边界

默认允许的只读验证

在授权环境中使用 SELECT、受限 JOIN、COUNT 和 SUM,并通过精确条件限制范围。

需要额外授权的操作

INSERT、UPDATE、DELETE、锁表、事务实验和并发压测必须在测试环境按方案执行,不能复制到生产环境。

09

从差异反推问题所在层级

先定位,再报缺陷
沿数据链路逐层对比,可以把差异缩小到具体位置
01页面

用户看到了什么

02接口

服务返回了什么

03数据库

最终保存了什么

04日志与事件

中间发生了什么

根据差异表现选择下一步证据

看到的现象优先怀疑继续收集
页面金额错误,接口也错误金额计算服务或规则配置请求参数、服务日志、订单明细
接口正确,数据库错误落库映射、精度或事务SQL 参数、字段类型、提交与回滚日志
数据库正确,页面错误接口转换、缓存或前端展示接口响应、缓存键、前端格式化
偶发库存错误并发更新或消息重复版本号、幂等键、事务日志、消息 ID

找出订单主表与明细汇总不一致的记录

SELECT
  o.order_id,
  o.total_amount,
  COALESCE(SUM(i.quantity * i.unit_price), 0) AS item_total
FROM orders AS o
LEFT JOIN order_items AS i
  ON i.order_id = o.order_id
WHERE o.created_at >= '2026-08-09 00:00:00'
GROUP BY o.order_id, o.total_amount
HAVING o.total_amount <>
  COALESCE(SUM(i.quantity * i.unit_price), 0);

记录足够复现的信息

  • 测试环境、版本、执行时间和测试账号。
  • 订单号、商品 SKU、幂等键和本轮测试标识。
  • 页面截图、接口请求与响应、实际 SQL 结果。
  • 预期关系、实际差异和受影响的数据范围。
10

把 SQL 变成可重复执行的校验

查询结果要能判定
可执行校验需要明确输入、查询、判断和证据
INPUT输入

订单号与测试标识

QUERY查询

只读 SQL

ASSERT判断

0 行或明确差值

EVIDENCE证据

保存失败记录

用查询直接返回失败记录

SELECT
  o.order_id,
  o.pay_amount,
  o.total_amount - o.discount_amount AS expected_pay
FROM orders AS o
WHERE o.test_run_id = 'RUN_20260809_001'
  AND o.pay_amount <>
      o.total_amount - o.discount_amount;

-- 通过标准:返回 0 行;有返回值时,每一行都是待定位差异

练习:独立完成一次下单数据核对

  1. 准备一个有 5 件库存、单价 60 元并可使用满减券的商品。
  2. 记录操作前库存和优惠券状态,再通过页面购买 2 件商品。
  3. 使用 SELECT 和 WHERE 找到目标订单。
  4. 使用 JOIN 查询订单明细和优惠券记录。
  5. 使用 GROUP BY 重新计算商品数量和原始金额。
  6. 核对实付金额、库存变化和优惠券状态。
  7. 重复提交同一请求,确认订单数和库存不会再次变化。
  8. 把查询整理为返回失败记录的可执行校验。

查询可控

  • 只查本轮数据
  • 字段范围明确
  • 时间边界明确
  • 结果顺序稳定

结果可信

  • 记录操作前基线
  • 重新计算关键金额
  • 跨表关系已核对
  • 失败回滚已检查

执行安全

  • 环境和权限确认
  • 默认使用只读 SQL
  • 测试数据可识别
  • 修改操作有回收方案

能够独立核对下单数据后,继续学习 Python 与 pytest,把手工校验转化为可重复执行的测试代码。

继续学习 Python 与 pytest