SQL Server中必须用SCOPE_IDENTITY()获取主表新ID,因其仅返回当前作用域内最后一次INSERT生成的自增ID,避免触发器干扰;若插入失败、非IDENTITY列或跨作用域调用则返回NULL。

SQL Server 中用 SCOPE_IDENTITY() 获取主表新ID
主从表插入最核心的问题是:子表外键必须指向刚插入的主表记录ID。在 SQL Server 里,@@IDENTITY 和 SCOPE_IDENTITY() 都能取刚生成的自增ID,但前者受触发器干扰(比如主表有触发器往另一张日志表插入,@@IDENTITY 就会返回日志表的ID),所以必须用 SCOPE_IDENTITY()。
常见错误现象:@newId 取出来是 NULL 或错误值,导致子表插入失败或关联错乱。
- 确保主表主键是
IDENTITY列,否则SCOPE_IDENTITY()返回NULL - 插入语句和
SCOPE_IDENTITY()必须在同一个作用域(同一BEGIN...END块内) - 不要在插入后加任何其他
INSERT/UPDATE操作,哪怕只是往临时表插一行,也会污染作用域
MySQL 中靠 LAST_INSERT_ID() 实现链式写入
MySQL 没有作用域概念,LAST_INSERT_ID() 是会话级的,只要没被其他 INSERT 覆盖,就能安全复用。但它只对自增列有效,且要求插入语句本身触发了自增(比如没显式指定 ID 值)。
容易踩的坑:
- 如果主表插入用了
INSERT ... SELECT或批量插入多行,LAST_INSERT_ID()只返回第一行的 ID,不是最后一条 - 在存储过程中调用其他也含
INSERT的子过程,可能意外覆盖该值 - 不建议在同个事务里混用
INSERT INTO ... VALUES()和INSERT INTO ... SELECT,行为不易预测
示例中常用模式:INSERT INTO main_table (...) VALUES (...); SET @main_id = LAST_INSERT_ID(); INSERT INTO detail_table (main_id, ...) VALUES (@main_id, ...);
事务包裹 + 错误处理是硬性前提
主从插入不是两个独立操作,而是一个原子动作。一旦主表成功、子表失败,数据库就处于不一致状态——比如产线主记录存在,但三条明细线缺了一条。
必须显式使用事务控制:
- 开头加
BEGIN TRY ... BEGIN TRANSACTION,结尾配COMMIT TRANSACTION和END TRY ... END CATCH -
CATCH块里必须包含IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION,否则异常后事务挂起,连接可能被锁死 - 避免在事务中调用外部服务、写文件、发邮件等非数据库操作,它们无法回滚
很多人图省事把事务去掉,测试时看似没问题,上线后并发一高,数据错位就很难排查。
批量明细插入别硬拼字符串,优先用表值参数或临时表
看到用 varchar(max) 拼接 'a|b|c' 再循环解析的写法,性能和可维护性都差。SQL Server 支持表值参数(TVP),MySQL 可建临时表,这才是正解。
对比:
- 字符串分割方式:每次调用标量函数(如
dbo.F_RtnStrBySplitIndex)都会引发额外执行计划开销,100 条明细可能触发 100 次函数调用 - TVP 方式:客户端一次性传入
DataTable或LIST,SQL Server 直接当表用,执行计划稳定 - 临时表方式(MySQL):先
CREATE TEMPORARY TABLE tmp_detail (...),再INSERT INTO detail_table SELECT @main_id, ... FROM tmp_detail
真正难的不是语法,而是怎么让主从插入在高并发下不丢数据、不锁表、不出错——事务隔离级别、索引设计、主键类型这些底层细节,比“怎么写”更关键。











