在插入 PHP 后获取生成的 UUID。

10
我有一个表字段类型为varchar(36),我想通过MySQL动态生成它,所以我使用了以下代码:
$sql_code = 'insert into table1 (id, text) values (uuid(),'some text');';
mysql_query($sql_code);

如何在插入记录后立即检索生成的uuid?

1
你的id字段是唯一的吗? - Pekka
@Pekka:你认为在我们的宇宙中可能发生UUID碰撞吗?;-) - zerkms
@zerkms 不是的 :) 但是如果我没记错的话,LAST_INSERT_ID() 只能在唯一列上工作,因此我才问。 - Pekka
@Pekka:更重要的是,它只适用于自增的那些,如果我没记错的话,这些自增的本质上是唯一的。 - zerkms
@zerkms 哦,好的。 - Pekka
4个回答

26
  1. char(36)更好。
  2. 你不能这样做。唯一的解决方法是执行2个单独的查询:

    • SELECT UUID()
    • INSERT INTO table1 (id, text) VALUES ($uuid, 'text')

其中$uuid是在第一步中检索到的值。


5
char(36)并没有太大的改进,binary(16)会更有效率。 - pospi
@pospi:char(36)比varchar更好。而且使用二进制时,您将在字符串比较方面遇到问题(二进制不尊重排序规则)。 - zerkms
1
这是正确的,但如果您正在插入二进制数据,则不会像字符串一样处理它或期望比较按照那种方式工作 - 您将使用HEX()读取并使用十六进制表示法或UNHEX()进行查询...并且您在字段之间执行的任何比较都将在原始二进制数据上执行,而不是在其字符上。 - pospi
CHAR(36)在UTF-8编码下会变成3*36个字节长,再加上长度字节。因此,你最好的选择是(A)二进制类型、(B)ASCII CHAR类型或者(C)VARCHAR类型。 - Timo
UUID() 是数据,而不是列定义。列宽取决于您的表/列字符集。CHAR(36) 的大小取决于可能包含在其中的数据(由字符集定义),而不是您此时刚好要存储的数据。是的,对于 UUID() 来说,它并不需要如此大的空间。但是(如果使用 UTF-8),您随时可以将其中存储 36 个日语字符,这需要更多的空间,而该列已经保留了这种可能性的空间,无论您是否使用它。这是使用 UTF-8 CHAR 应谨慎的原因。 - Timo
显示剩余2条评论

11

你可以通过SQL触发器完成所需的一切操作。以下SQL在tablename.table_id上添加了一个触发器,以便在插入时自动创建主键UUID,然后将新创建的ID存储到SQL变量中以供稍后检索:

CREATE TRIGGER `tablename_newid` 
AFTER INSERT ON `tablename` 
FOR EACH ROW 
BEGIN 
    IF ASCII(NEW.table_id) = 0 THEN 
        SET NEW.table_id = UNHEX(REPLACE(UUID(),'-','')); 
    END IF; 
    SET @last_uuid = NEW.table_id; 
END

作为奖励,它将UUID以二进制形式插入到二进制(16)字段中,以节省存储空间并大大提高查询速度。
编辑:在插入自己的UUID之前,触发器应检查现有列值,以模仿在MySQL中为表主键提供值的能力——如果没有这个,任何传递的值都将被触发器覆盖。已更新示例以使用ASCII() = 0来检查INSERT中主键值的存在,这将检测到二进制字段的空字符串值。
编辑2:在这里的评论后,有人指出在使用BEFORE INSERT时,即使行插入失败,也会设置@last_uuid变量的效果。我已经更新了我的答案,使用AFTER INSERT——虽然我认为这是一个完全可以接受的方法,在一般情况下可能会在集群或复制数据库下出现行复制问题。如果有人知道,我也很想知道!
要读取新行的插入ID,只需运行SELECT @last_uuid。
当查询和读取这样的二进制值时,MySQL函数HEX()和UNHEX()将非常有帮助,编写查询值的十六进制表示法(以0x开头)也将非常有帮助。针对table1应用此类触发器的原始答案的php端代码将是:
// insert row
$sql = "INSERT INTO table1(text) VALUES ('some text')";
mysql_query($sql);

// get last inserted UUID
$sql = "SELECT HEX(@last_uuid)";
$result = mysql_query($sql);
$row = mysql_fetch_row($result);
$id = $row[0];

// perform a query using said ID
mysql_query("SELECT FROM table1 WHERE id = 0x" . $id);

回复@ina的评论

UUID并不是一个字符串,即使MySQL选择将其表示为字符串。它在原始形式下是二进制数据,那些破折号只是MySQL以友好的方式向您表示它。

最有效的UUID存储方法是将其创建为UNHEX(REPLACE(UUID(),'-''))- 这将删除该格式并将其转换回二进制数据。这些函数将使原始插入变慢,但您对该键或列执行的所有后续比较都将在16字节的二进制字段上比36个字符的字符串快得多。

首先,字符数据需要解析和本地化。任何传入查询引擎的字符串通常会与数据库的字符集自动匹配,并且一些API(例如wordpress)甚至在查询之前对所有字符串数据运行CONVERT()。二进制数据没有这种开销。其次,您的char(36)实际上分配了36个字符,这意味着(如果您的数据库是UTF-8),每个字符的长度可能为3或4个字节,具体取决于您使用的MySQL版本。因此,char(36)的大小范围可以从36个字节(如果完全由低ASCII字符组成)到144个字节(如果完全由高阶UTF8字符组成)。这比我们为二进制字段分配的16个字节要大得多

任何在这个数据上执行的逻辑都可以使用UNHEX()来完成,但更好的方法是在查询中将数据作为十六进制进行转义,并在前面加上0x。这样做与读取字符串一样快速,并在转换为二进制后直接分配给相关的查询或单元格。非常快。 从数据中读取数据略慢一些 —— 必须对从查询中读出的所有二进制数据调用HEX()才能以有用的格式获得它们,如果你的客户端 API 无法很好地处理二进制数据(尤其是 PHP 通常会确定二进制字符串 === null 并且如果未经过第一次调用bin2hex()base64_encode()或类似操作就对它们进行操作,则会破坏它们)—— 但这种开销与字符排序一样小,更重要的是,它仅在实际选择的单元格上被调用,而不是在查询结果的所有涉及单元格上被调用。 当然,所有这些小的速度增益都非常微小,而其他领域的效果则略微降低。但当你把它们全部加起来时,binary仍然是最好的选择,特别是在考虑到使用情况和一般的“读取 > 写入”原则时,它真的很出色。 这就是为什么binary(16)char(36)更好的原因。

这不仅仅是MySQL决定如此显示UUID,而是其被推荐的表示方法。 - zerkms
1
即使如此,它仍然只是用于表示。所有这些破折号仅表示UUID的哪些部分是从时间戳生成的,哪些保留了唯一性并且基于主机机器的MAC地址。您不需要从UUID中了解任何此类信息的原因,也没有任何理由如果您知道正在生成的字节偏移和UUID变体,那么您无法找出它们。如果有什么,删除它们只会进一步混淆从该ID派生任何此类信息的可能性。 - pospi
实际上分配了36个字符,这意味着(如果您的数据库是UTF-8)每个字符要么是3个字节,要么是4个字节——这是错误的。对于ASCII安全字符的UTF-8字符串将恰好占用36个字节。 - zerkms
我已根据BEFORE INSERT触发器的一些评论更新了我的答案,请参见edit2注释(: - pospi

6

其实很简单,你可以将它传递给MySQL,它会返回插入的ID。

set @id=UUID();
insert into <table>(<col1>,<col2>) values (@id,'another value');
select @id;

如果插入失败会发生什么?您仍然会得到ID返回吗?它会掩盖插入失败吗?我知道这个答案很旧了。只是为了让那些还在寻找答案的人看到它(我就是其中之一)。 - Cully
@Cully,这取决于您使用的库或语言。如果插入失败,一些语言会抛出异常,在您的代码中,您需要捕获异常,而一些框架只会将错误字符串作为输出抛出。因此,很多情况下都取决于您用来访问数据库的框架。 - roneo

-1

根据uuid()函数的实现方式,这是非常糟糕的编程实践 - 如果您尝试在启用二进制日志记录(即在集群中)的情况下执行此操作,则插入将很可能失败。Ivan的建议似乎可以解决即时问题 - 但我认为这仅返回自动递增字段生成的值 - 的确,这就是手册所说的

另外,使用uuid()有什么好处?它的计算成本很高,需要大量存储空间,增加了查询数据的成本,并且不具备密码学安全性。请改用序列生成器或自动递增。

无论您使用序列生成器还是uuid,如果您必须将其用作数据库上唯一的键,则需要先分配该值,将其读回到phpland中,并将该值作为文字嵌入/绑定到后续的插入查询中。


这并不是一种坏的做法。这只是解决问题的另一种方式:http://www.codinghorror.com/blog/2007/03/primary-keys-ids-versus-guids.html - zerkms
PK和加密货币有什么关系?! - zerkms
当然,二进制日志记录对于群集或复制中的 uuid 不会造成任何问题。 - zerkms
1
我已经在MySQL Cluster和Gallera上测试了我编写的触发器,没有任何问题。它处理INSERT触发器上UUID键的存在意味着负责生成行的节点在运行其事务时创建该键,并且当数据级联到复制表时,该键存在于提供给其他节点的数据中。 - pospi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接