MySQL 基础 + AI SQL 优化

楠风测开 · 测试工程师复习日记 Day15

给SQL建立可回归的业务契约,再讨论AI改写和执行效率。

前言

朋友们好,我是楠风。今天从Linux阶段转到MySQL。

做接口自动化时,我们经常增加一个数据库断言:接口成功后,查询订单表,确认记录存在。但存在性检查只覆盖了最小的一部分。状态迁移、金额、支付流水、库存,以及失败后的回滚,才共同构成业务结果。

这篇用一个订单报表说明:怎么把SQL从"能运行"推进到"能回归",又怎样审核AI给出的优化建议。示例以MySQL 8.4、InnoDB为参照,使用合成数据;没有本机MySQL性能实测,也不提供虚构的提速比例。

一、先为查询写一份业务契约

假设我们有四张表:orders保存订单,order_items保存商品明细,payments保存支付尝试,products保存商品当前价格与库存。

先把需求写成可以检查的规则:

text 复制代码
输出粒度:每张订单一行。
覆盖范围:保留已支付、待支付、取消的订单。
明细金额:数量 × 下单时单价,再按订单汇总。
成功支付:只统计status='SUCCESS'的支付记录。
缺失支付:报表展示为0,但订单仍要保留。
顺序要求:按订单ID排列,便于固定样例逐行比较。

这里的规则是教学场景,不代表所有电商系统。退款、部分支付、优惠分摊、币种换算都可能改变核对口径,接入真实系统前应单独明确。

金额字段使用DECIMAL(10,2),避免用近似浮点值表达这组两位小数金额。还要固定舍入位置;"每行先舍入再加总"和"合计后舍入"可能有不同结果。MySQL精确数值类型

二、让样例数据主动暴露问题

只造一张订单、一条明细、一笔支付,很多错误SQL也能通过。

我们至少准备以下四种记录:

订单 明细 支付记录 应检查的现象
1001 键盘2件,合计199.80 一次失败、一次成功 失败支付不计入金额
1002 鼠标1件,59.90 没有记录 订单不能被关联过滤
1003 键盘1件,99.90 一次失败,订单已取消 无成功支付不等于仍可支付
1004 键盘和鼠标,合计159.80 成功支付99.90和59.90 两个一对多关系不能重复计数

金额都来自固定样例,可以手算预期。性能数据则不能这样手算成"实测结果"。正确性夹具和性能造数最好分开管理,后续Day16的十万行表专门用于访问路径实验。

三、复现一次一对多连接导致的金额翻倍

下面用CTE临时构造1004的数据,不依赖已有表,不修改数据库。复制到MySQL 8.4查询窗口即可用于核对预期。

sql 复制代码
WITH orders_demo AS (
  SELECT 1004 AS id, 159.80 AS total_amount
), items_demo AS (
  SELECT 1004 AS order_id, 1 AS quantity, 99.90 AS unit_price
  UNION ALL SELECT 1004, 1, 59.90
), payments_demo AS (
  SELECT 1004 AS order_id, 99.90 AS amount
  UNION ALL SELECT 1004, 59.90
)
SELECT o.id, COUNT(*) AS joined_rows,
       SUM(i.quantity * i.unit_price) AS item_total,
       SUM(p.amount) AS paid_total
FROM orders_demo AS o
JOIN items_demo AS i ON i.order_id = o.id
JOIN payments_demo AS p ON p.order_id = o.id
GROUP BY o.id;

预期是4行组合、两个合计均为319.60。这是一个故意展示错误的查询。原因不是SUM失灵,而是聚合之前,商品明细和支付都被连接放大了。

修复时先约束中间结果的粒度:

sql 复制代码
WITH orders_demo AS (
  SELECT 1004 AS id, 159.80 AS total_amount
), items_demo AS (
  SELECT 1004 AS order_id, 1 AS quantity, 99.90 AS unit_price
  UNION ALL SELECT 1004, 1, 59.90
), payments_demo AS (
  SELECT 1004 AS order_id, 99.90 AS amount
  UNION ALL SELECT 1004, 59.90
), item_sum AS (
  SELECT order_id, SUM(quantity * unit_price) AS amount
  FROM items_demo GROUP BY order_id
), payment_sum AS (
  SELECT order_id, SUM(amount) AS amount
  FROM payments_demo GROUP BY order_id
)
SELECT o.id, o.total_amount,
       COALESCE(i.amount, 0.00) AS item_total,
       COALESCE(p.amount, 0.00) AS paid_total
FROM orders_demo AS o
LEFT JOIN item_sum AS i ON i.order_id = o.id
LEFT JOIN payment_sum AS p ON p.order_id = o.id
ORDER BY o.id;

预期订单、明细、支付三个金额都是159.80。生产表中的payment_sum还要按业务定义筛选成功状态;这里构造的两条记录已经明确都是成功支付。

再加一组反例:把两条支付都改成79.90。正确合计仍是159.80,而SUM(DISTINCT amount)会得到79.90。因此,按金额去重不是修复交易重复的方法。

四、把NULL、筛选和排序也纳入回归

金额正确后,还不能只比较结果总行数。

左连接的筛选位置

下面是业务表的查询片段:

sql 复制代码
SELECT o.id, COUNT(p.id) AS success_count
FROM orders AS o
LEFT JOIN payments AS p
  ON p.order_id = o.id AND p.status = 'SUCCESS'
GROUP BY o.id
ORDER BY o.id;

它保留没有成功支付的订单。如果AI把状态筛选移动到WHERE,空匹配订单会丢失。COUNT(p.id)为0与COUNT(*)统计外连接保留行也不同。JOIN语义 · 聚合函数说明

空值与业务状态

paid_at IS NULL用于检查缺失支付时间,不意味着订单一定还可以付款。取消订单也可能符合这个条件。代码里把空值转换成0或空字符串之前,要明确这样转换是否会掩盖数据异常。NULL语义

日期边界与稳定排序

sql 复制代码
SELECT id, status, total_amount
FROM orders
WHERE user_id = 101
  AND created_at >= '2026-10-01 00:00:00'
  AND created_at <  '2026-10-02 00:00:00'
ORDER BY created_at, id;

在样例约定的同一时区下,验证当天零点、次日零点、同一时间多张订单这三种情况。使用分页时尤其需要稳定排序;改写后第一屏看起来相同,不代表后续页不会漏或重复。

五、数据库断言还应覆盖事务结果

查询只能告诉我们当前状态,事务演练用于确认修改是否作为一个整体完成。

以扣减2件键盘、创建订单为例,应用逻辑应明确成下面的流程。它是伪代码,异常处理要按实际语言实现:

text 复制代码
开启事务
尝试UPDATE products SET stock=stock-2
  WHERE sku='KEYBOARD' AND stock>=2
读取本次UPDATE的受影响行数
如果不是1:回滚并结束本次下单
写入订单和明细
如果任意一步异常:回滚并返回失败
只有全部成功:提交

这里的受影响行数要紧接更新读取。不要先执行别的语句,再把已经改变含义的返回值拿来判断库存。

自动化用例至少覆盖成功提交、库存不足、订单号重复、明细写入失败。每个用例记录初始库存和实验订单号,结束后检查订单、明细、库存三个结果,不能只断言接口报错。

给单库事务包上try/except并不自动解决跨服务一致性。第三方支付、消息和邮件有自己的副作用,数据库回滚不会替它们撤销操作。事务控制说明

另外,索引和表结构变更属于另一个边界:一些DDL会隐式提交,不能把建索引放进业务回滚用例里,期待最后一句ROLLBACK把它撤掉。隐式提交说明

六、给AI一个有约束的SQL评审任务

我的建议是让AI输出"候选修改与验证计划",而不是直接要求它宣布优化成功。

text 复制代码
请评审下面的SQL,环境是MySQL 8.4 / InnoDB。
我会提供表DDL、索引、脱敏数据、原SQL、业务预期。

第一轮只做语义检查:
- 每张订单一行,不能丢失无支付订单;
- 两笔同额合法支付不能被去重;
- 时间范围、NULL和状态规则不能暗中改变;
- 给出错误SQL的最小反例和预期值。

第二轮再做性能候选:
- 说明需要什么执行计划或统计证据;
- 给出一次只改一个因素的比较方法;
- 无实测时不填写耗时和提效结论。

应用里执行动态SQL时还要使用参数绑定,不要为了让AI示例看起来完整,就把用户输入拼接进SQL文本。占位符格式由具体驱动决定,不能混用不同语言的写法。

评审后保留三份材料:原始需求、候选SQL、验收记录。发现问题时,这三份材料能说明是哪条业务规则被改变,而不是停留在"AI生成的代码有问题"。

七、从单次查询到可回归检查

对固定夹具,建议按唯一订单ID逐行比较结果:字段集合、状态、金额、空值、重复次数以及业务需要的顺序。金额在程序中使用十进制定点类型或项目统一金额表示,不要先转浮点再做严格相等断言。

若旧SQL本来有错,比较目标应该是业务预期,不是让新版本和错误结果保持一致。四表报表还应单独检查无明细订单;把缺失金额展示为0之后,不能失去对异常关系的判断。

性能比较放在正确性通过之后。先看EXPLAIN,再决定是否在隔离实验环境运行EXPLAIN ANALYZE;后者确实会执行查询,不是只展示静态建议。EXPLAIN官方说明

最后留一份小型交付

这一篇完成后,项目里应留下四样东西:一份数据关系说明、一组能暴露问题的夹具、正确查询与反例,以及事务成功/失败的验收记录。

它们也是后面做接口自动化平台时可以复用的资产:断言依据、数据准备、失败证据,都能从这里继续长出来。

下一篇 Day16《MySQL 索引 + AI 索引建议》,我们用独立的十万行实验表看查询路径和索引代价。你会优先补哪一类数据库断言:金额、状态、库存,还是重复请求?欢迎分享你的取舍。

相关推荐
Sperains1 小时前
换电脑不用重配:WSL 环境的备份、验证与迁移
程序员
楠风测开4 小时前
Linux 监控告警 + AI 智能监控
程序员
Solara5 小时前
库连续 35 天只写自己的日记:第 36 天的外采,进得了索引,进不了版本库
人工智能·程序员·ai编程
阿里嘎多学长5 小时前
2026-10-14 GitHub 热点项目精选
开发语言·程序员·github·代码托管
合尘猫5 小时前
给客服系统加一道闸:把「该转人工」从模糊判断变成可测拦截
人工智能·后端·程序员
阿里嘎多学长6 小时前
2026-10-13 GitHub 热点项目精选
开发语言·程序员·github·代码托管
程序员cxuan1 天前
Gemini 4 Argon 正式发布!
人工智能·后端·程序员
高晶1 天前
ARM 嵌入式 Linux 开发:从体系结构到 LED 点亮实战
设计模式·程序员