sql server用户定义函数中禁止使用newid(),因其属于副作用操作;存储过程则允许set @id = newid()赋值并用于insert等操作。

SQL Server 存储过程中不能直接在函数体里调用 NEWID() 赋值给变量——这是语法硬限制,不是写法问题。
为什么存储过程里不能用 NEWID() 做函数式赋值
SQL Server 明确禁止在用户定义函数(UDF)中使用任何“副作用”操作,而 NEWID() 属于 side-effecting operator。哪怕你只是想在存储过程中用 SELECT @uuid = NEWID(),只要这个逻辑被封装进 CREATE FUNCTION 里,就会报错:Invalid use of side-effecting or time-dependent operator in 'newid'。
注意:这个限制只针对 FUNCTION,PROCEDURE 本身是允许的——但很多人误把“想在函数里生成 UUID”当成“在存储过程中生成 UUID”,结果卡在定义阶段。
- 存储过程(
PROCEDURE)可以安全使用SET @uuid = NEWID()或SELECT @uuid = NEWID() - 标量函数(
FUNCTION)哪怕只有一行RETURN NEWID(),也必定失败 -
NEWSEQUENTIALID()同样不被允许出现在函数中,且它还不能用于INSERT ... VALUES场景
在存储过程中正确生成并使用 UUID 的写法
真正可行的做法是:在存储过程内部用变量承接,再参与后续逻辑。关键不是“能不能生成”,而是“怎么接、怎么传、怎么存”。
- 声明变量必须是
UNIQUEIDENTIFIER类型:DECLARE @id UNIQUEIDENTIFIER - 赋值必须用
SET或SELECT:SET @id = NEWID()(推荐),不要写SET @id = (SELECT NEWID()) - 插入时直接用变量:
INSERT INTO users (id, name) VALUES (@id, 'Alice') - 如果要返回,用
OUTPUT子句或SELECT @id,别试图从函数里“取值”
示例片段:
CREATE PROCEDURE sp_CreateUser
@name NVARCHAR(50),
@out_id UNIQUEIDENTIFIER OUTPUT
AS
BEGIN
DECLARE @id UNIQUEIDENTIFIER;
SET @id = NEWID();
INSERT INTO users (id, name) VALUES (@id, @name);
SET @out_id = @id;
END
常见踩坑点:默认值 + 变量 + 插入语句混用
很多人想“一步到位”:建表时设 DEFAULT NEWID(),又在存储过程中手动 INSERT ... VALUES (NEWID(), ...),结果发现主键重复或触发两次生成——其实根本没冲突,只是理解错了行为边界。
-
DEFAULT NEWID()只在INSERT语句**省略该列**时生效;显式写了值,就完全绕过默认值 - 在存储过程中既设了默认值,又手动传
NEWID(),等于自己多算一次,无意义 - 如果表结构用的是
NEWSEQUENTIALID()做默认值,那在存储过程中调用NEWID()会得到完全无序的值,破坏局部顺序性预期 - 别用
CAST(NEWID() AS VARCHAR(36))再处理——直接用REPLACE(CAST(@id AS VARCHAR(36)), '-', '')更稳妥,避免隐式转换截断
替代方案:什么时候该放弃存储过程内生成
如果你实际需要的是“每次调用都返回一个新 UUID 字符串”,而不是“在事务中绑定某个实体 ID”,那存储过程反而是过度设计。
- 纯生成需求,用应用层(如 C# 的
Guid.NewGuid().ToString("N"))更轻量、可控、可测 - 需要保证插入原子性+返回 ID,优先考虑表级
DEFAULT NEWID()+OUTPUT INSERTED.id,比变量赋值更可靠 - 高并发写入场景下,
NEWID()导致的索引页分裂比想象中严重,此时应评估NEWSEQUENTIALID()是否适用(注意:它不能用在VALUES子句)
最常被忽略的一点:UUID 是二进制值,UNIQUEIDENTIFIER 类型存储就是 16 字节;一旦转成字符串再存,就失去类型优势,索引比较、JOIN 效率全掉档。










