摘要:线上 MySQL 生产环境几乎不再使用物理外键约束,依靠字段注释、文档维护逻辑关联是行业主流。本文基于 Chat2DB 社区版 5.3.8,搭建一套无物理外键的电商测试数据集,验证 Chat2DB 原生自带的
@表名唤起表语法,测试工具在仅依靠注释、逻辑关联描述的前提下,自动识别表关联、一对多聚合、退款口径过滤的能力,同时复盘自然语言需求歧义带来的边界问题。
1、背景:为什么生产不用物理外键?
传统教学场景写 SQL 时习惯增加 FOREIGN KEY 物理外键,但在互联网业务、大数据数仓场景,物理外键有明显短板:
- DML 操作会触发外键校验,增加行锁,高并发下容易产生锁等待;
- 分库分表、数据迁移、批量数据导入时,外键约束会阻碍数据写入;
- 业务变更、数据归档、软删除场景,外键约束维护成本极高;
所以目前主流方案:仅保留主键,通过字段注释、业务文档维护表与表之间的逻辑关联关系 ,也就是逻辑外键。 而 Text2SQL 工具(Chat2DB)想要在生产落地,核心考验就是:能不能不靠数据库元数据的物理外键,仅依靠注释、表关系描述,自动完成正确的多表 JOIN。
2、测试数据集设计
我们搭建一套电商订单场景,包含会员、商品、订单主表、订单明细、售后退款 5 张表,所有关联依靠注释描述,无任何外键约束。
2.1 DDL 建表语句
-- 会员表 member
CREATE TABLE member (
member_id BIGINT PRIMARY KEY COMMENT '会员ID【逻辑主键】',
member_name VARCHAR(50) NOT NULL COMMENT '会员名称',
member_level TINYINT COMMENT '会员等级 0普通 1银卡 2金卡 3钻石',
register_time DATETIME COMMENT '注册时间',
phone VARCHAR(11) COMMENT '手机号'
) ENGINE=InnoDB COMMENT='会员基础表';
-- 商品表 goods
CREATE TABLE goods (
goods_id BIGINT PRIMARY KEY COMMENT '商品ID【逻辑主键】',
goods_name VARCHAR(100) NOT NULL COMMENT '商品名称',
category_id INT COMMENT '分类ID',
sale_price DECIMAL(10,2) COMMENT '售卖单价',
cost_price DECIMAL(10,2) COMMENT '成本单价',
stock_num INT COMMENT '当前库存数量'
) ENGINE=InnoDB COMMENT='商品基础表';
-- 订单主表 order_main
CREATE TABLE order_main (
order_id BIGINT PRIMARY KEY COMMENT '订单号【逻辑主键】',
member_id BIGINT COMMENT '会员ID,逻辑关联 member.member_id',
order_time DATETIME COMMENT '下单时间',
pay_time DATETIME COMMENT '支付时间',
total_amount DECIMAL(12,2) COMMENT '订单实付总金额',
order_status TINYINT COMMENT '订单状态 0待支付 1已支付 2已发货 3已完成 4已取消',
warehouse_id INT COMMENT '发货仓库ID'
) ENGINE=InnoDB COMMENT='订单主表,一个会员多条订单';
-- 订单明细表 order_item
CREATE TABLE order_item (
item_id BIGINT PRIMARY KEY COMMENT '明细ID【逻辑主键】',
order_id BIGINT COMMENT '订单ID,逻辑关联 order_main.order_id',
goods_id BIGINT COMMENT '商品ID,逻辑关联 goods.goods_id',
buy_num INT COMMENT '购买数量',
item_price DECIMAL(10,2) COMMENT '下单快照单价'
) ENGINE=InnoDB COMMENT='订单明细,一条订单多条明细,一条明细对应一个商品';
-- 售后退款表 refund_record
CREATE TABLE refund_record (
refund_id BIGINT PRIMARY KEY COMMENT '退款记录ID【逻辑主键】',
order_id BIGINT COMMENT '订单ID,逻辑关联 order_main.order_id',
refund_amount DECIMAL(12,2) COMMENT '本次退款金额',
refund_time DATETIME COMMENT '退款时间',
refund_status TINYINT COMMENT '0待审核 1退款成功 2驳回'
) ENGINE=InnoDB COMMENT='售后退款表,一个订单可以多次发起退款';
- 逻辑外键:必须在注释上写清楚关联关系,如:'会员ID,逻辑关联 member.member_id'。
2.2 逻辑关联说明
逻辑关联关系:
- member.member_id <--> order_main.member_id:一对多,1个会员可以有多条订单
- order_main.order_id <--> order_item.order_id:一对多,1个订单对应多条商品明细
- goods.goods_id <--> order_item.goods_id:一对多,一个商品可以出现在多条订单明细
- order_main.order_id <--> refund_record.order_id:一对多,一个订单支持多次退款记录
所有关联均为逻辑关联,无数据库物理外键。
2.3 测试数据
数据设计思路:制造一个订单多条退款、退款有驳回状态、待支付订单、已取消订单等边界场景,专门用来检验一对多联表求和时会不会出现金额膨胀。
INSERT INTO member(member_id,member_name,member_level,register_time,phone) VALUES
(1001,'张三',3,'2025-03-12 09:20:00','13800001001'),
(1002,'李四',2,'2025-05-20 14:10:00','13800001002'),
(1003,'王五',1,'2026-01-08 10:30:00','13800001003'),
(1004,'赵六',0,'2026-06-15 16:40:00','13800001004'),
(1005,'孙七',3,'2025-11-02 11:00:00','13800001005');
INSERT INTO goods(goods_id,goods_name,category_id,sale_price,cost_price,stock_num) VALUES
(2001,'机械键盘',101,299.00,120.00,120),
(2002,'无线鼠标',101,89.00,35.00,300),
(2003,'27寸显示器',102,1299.00,580.00,45),
(2004,'笔记本支架',103,49.00,18.00,500),
(2005,'降噪耳机',104,499.00,210.00,88);
INSERT INTO order_main(order_id,member_id,order_time,pay_time,total_amount,order_status,warehouse_id) VALUES
(30001,1001,'2026-10-02 08:30:00','2026-10-02 08:32:00',1598.00,3,1),
(30002,1001,'2026-10-05 15:10:00','2026-10-05 15:15:00',299.00,2,1),
(30003,1002,'2026-10-08 09:05:00','2026-10-08 09:07:00',588.00,3,2),
(30004,1002,'2026-10-10 11:20:00',NULL,499.00,0,2),
(30005,1003,'2026-10-12 16:00:00','2026-10-12 16:03:00',1348.00,3,1),
(30006,1004,'2026-10-15 10:40:00','2026-10-15 10:42:00',49.00,4,3),
(30007,1005,'2026-10-18 14:25:00','2026-10-18 14:28:00',1798.00,3,2),
(30008,1005,'2026-10-22 17:10:00','2026-10-22 17:12:00',89.00,1,1);
INSERT INTO order_item(item_id,order_id,goods_id,buy_num,item_price) VALUES
(40001,30001,2003,1,1299.00),
(40002,30001,2002,2,89.00),
(40003,30002,2001,1,299.00),
(40004,30003,2001,1,299.00),
(40005,30003,2002,1,89.00),
(40006,30003,2004,4,49.00),
(40007,30005,2003,1,1299.00),
(40008,30005,2004,1,49.00),
(40009,30006,2004,1,49.00),
(40010,30007,2003,1,1299.00),
(40011,30007,2005,1,499.00),
(40012,30008,2002,1,89.00);
INSERT INTO refund_record(refund_id,order_id,refund_amount,refund_time,refund_status) VALUES
(50001,30001,200.00,'2026-10-04 10:00:00',1),
(50002,30001,150.00,'2026-10-04 10:05:00',2),
(50003,30007,300.00,'2026-10-20 09:30:00',1),
(50004,30007,260.00,'2026-10-21 11:15:00',1);
3、AI提问生成sql
Chat2DB 输入框内置原生 @ 能力:在对话输入框输入 @,会弹出数据表下拉选择面板,选中后自动带入本次查询上下文,限定本次查询只使用标记的表。
3.1 提问内容
统计2026-10-01到2026-10-24,每个会员的:下单订单总数、订单总实付金额、成功退款总金额、净营收(实付总额-成功退款总额)
用到表:member order_main refund_record
3.2 回复结果

sql
SELECT
m.member_id,
m.member_name,
COUNT(DISTINCT o.order_id) AS order_cnt,
IFNULL(SUM(o.total_amount), 0) AS pay_amount,
IFNULL(SUM(r.refund_amt), 0) AS refund_amount,
IFNULL(SUM(o.total_amount), 0) - IFNULL(SUM(r.refund_amt), 0) AS net_revenue
FROM member m
LEFT JOIN (
SELECT order_id, member_id, total_amount
FROM order_main
WHERE order_time >= '2026-10-01 00:00:00'
AND order_time < '2026-10-25 00:00:00'
) o ON o.member_id = m.member_id
LEFT JOIN (
SELECT order_id, SUM(refund_amount) AS refund_amt
FROM refund_record
WHERE refund_status = 1
GROUP BY order_id
) r ON r.order_id = o.order_id
GROUP BY m.member_id, m.member_name
ORDER BY m.member_id;
- 核心亮点:自动识别一对多风险,先对子表 refund_record 预聚合求和,再关联订单主表,规避金额重复膨胀经典坑。
4、验证结果复盘
- 严格恪守字面需求:优先输出和提问完全匹配的 SQL 与结果,不擅自修改用户口径;
- 具备业务查漏补缺能力:识别自然语言里的歧义点,主动补充行业最常用的另一套口径对照表;
- 一对多陷阱识别到位:退款表先聚合再 JOIN,规避求和膨胀,是 Text2SQL 最容易翻车的点;
- 细节规范:左闭右开时间区间、LEFT JOIN 保维度不丢行、自动过滤驳回退款;
- 可审计:两套结果、差异原因、边界说明全部写清楚,使用者自己选择符合业务的口径,而不是模型替用户做决定。
5、落地最佳实践(Chat2DB + DeepSeek + 逻辑外键)
- 优先使用
@指定数据表:逻辑外键场景没有数据库外键元数据,缩小表范围是提升准确率的第一手段; - 规范注释 :关联字段写明
逻辑关联 xx表.xx字段,提前整理一对多 / 一对一关系; - 一对多子表强制预聚合:类似退款、明细这类子表,必须先 GROUP BY 聚合再 JOIN;DeepSeek 识别很好,复杂场景也可以在 prompt 主动强调;
- 善用双口径提示:当模型输出口径对照时,确认业务口径,把明确状态过滤条件补充到下一次提问;