得,今天又有的唠了。之前在搞数据库的时候,碰到个挺常见但也挺烦人的事儿,就是往一个带自增ID的表里塞了条新数据,然后我立马就想知道,刚才塞进去的那条,它的ID到底是几号。这需求听着简单,但实际操作起来,一开始还真让我挠头了一阵子。
我记得我可能会想着用 `SELECT MAX(ID) FROM 表名` 这种笨办法。但稍微想想就知道不靠谱,万一同一时间正好有别人也在插数据,那我拿到的可能就不是我刚插的那条的ID了,这在高并发场景下肯定得出问题,数据就乱套了。
踩坑与摸索
后来我知道了有个叫 `@@IDENTITY` 的东西,好像是能拿到插入的那个自增ID。我就试了试,有时候还真管用。但用着用着,就发现不对劲了。特别是有时候表上挂了触发器(Trigger),比如插完一条主表记录,触发器自动往日志表里也插条记录,而且日志表也有自增ID。这时候我用 `@@IDENTITY`,得,它返回的是日志表的那个ID,根本不是我想要的那个主表记录的ID。这一下就把我搞懵了,这不是我要的结果。
当时就觉得这玩意儿不保险,不稳定。于是我就继续找,看看有没有更精确、更靠谱的方法。总不能每次插数据都提心吊胆的。
发现新大陆:SCOPE_IDENTITY()
然后,经过一番折腾和查找,我就发现了 `SCOPE_IDENTITY()` 这个函数。看文档和一些大佬的分享,都说这东西好在哪?它返回的是当前会话和当前作用域里,一次执行 `INSERT` 语句生成的那个自增ID。
“当前作用域”这个词是关键!意思就是,它不受触发器之类的影响。就算我插入主表的操作触发了一个往别的表插数据的触发器,`SCOPE_IDENTITY()` 也只会认我当前这个 `INSERT` 语句直接影响的那张表的自增ID。它不会“串线”跑到触发器那边去。
这一下我就觉得稳了!这不就是我想要的效果嘛
上手实践
为了验证,我赶紧搭了个简单的环境试了试。
先建个主表,带自增ID:
CREATE TABLE MyMainTable (ID INT IDENTITY(1,1) PRIMARY KEY, Data VARCHAR(100));
再建个日志表,也带自增ID:
CREATE TABLE MyLogTable (LogID INT IDENTITY(100,1) PRIMARY KEY, Info VARCHAR(100));
然后搞个触发器,往主表插数据时,自动往日志表写日志:
AFTER INSERT AS BEGIN INSERT INTO MyLogTable (Info) VALUES ('New record added to MyMainTable'); END;CREATE TRIGGER trg_LogInsert ON MyMainTable
我就执行插入操作,并立马跟上 `SELECT SCOPE_IDENTITY();`:
SELECT SCOPE_IDENTITY();INSERT INTO MyMainTable (Data) VALUES ('测试数据');
跑了一下,结果出来了,返回的是 1(或者根据你表里已有的数据顺延)。然后我去查日志表 `MyLogTable`,里面确实也多了条记录,LogID 是 100。这下就清楚了,`SCOPE_IDENTITY()` 确实拿到了我插入 `MyMainTable` 的那个ID,没被触发器带偏。我又试了下 `SELECT @@IDENTITY;`,果然,它返回的是 100,是日志表的ID。
最终选择
经过这么一对比实践,结果就很明显了。在我的这个场景下,需要精确获取刚刚插入的那条记录自身的自增ID时,`SCOPE_IDENTITY()` 是最稳妥的选择。它能保证我拿到的就是我当前操作产生的那个ID,不受其他因素(主要是触发器)的干扰。
所以后来在我写的存储过程或者应用程序代码里,只要遇到插入数据后需要马上用到这个新ID的情况,比如需要拿着这个ID去更新其他关联表,我基本上就都用 `SCOPE_IDENTITY()` 了。用起来心里踏实多了。
对了,好像还有个 `IDENT_CURRENT('表名')` 的函数,不过那玩意儿是返回指定表的一个自增ID值,不管是谁、在哪个会话里插入的,它只看那个表当前的“最大”ID是多少,跟我这种需要“我刚才插入的那条记录的ID”的需求不是一回事儿,所以一般用不上。
这回实践让我对 `SCOPE_IDENTITY()` 有了挺深的认识,也解决了实际工作中一个不大不小的问题。搞明白了原理,用起来就顺手多了。分享出来,希望对可能遇到类似情况的朋友有点帮助。