问题:当我在数据库中使用自动递增主键时,会一直发生这种情况:
我想存储一个包含10个项目的订单。订购的商品属于订单。所以我存储了命令,向数据库询问最后插入的id(这对于并发是危险的,对吧?),然后使用外键(order_id)存储10个项目。
所以我总是这样做:
INSERT ...
last_inserted_id = db.lastInsertId();
INSERT ...... 插入 ... 插入...
我相信这使我无法在几乎所有需要外键的INSERT情况下使用事务。
所以...这里有一些解决方案,我不知道它们是否真的很好:
A)不要使用auto_increment键!使用密钥表?
密钥表将包含两个字段:table_name, next_key
。每次我需要一个表的键来插入一个新的数据集时,首先我通过访问一个特殊的静态KeyGenerator类方法来请求next_key。如果可能的话,这会在一个事务中执行SELECT和UPDATE (会有效吗?)。当然,我会要求每个受影响的桌子。接下来,在我事先知道密钥之前,我可以在一个事务中插入整个对象图,而不用与数据库打乒乓球。
B)使用GUUID / UUID算法进行密钥? 这些假设在世界范围内非常独特,而且它们很大。我的意思是...... L_A_R_G_E 。因此,大量的内存会进入这些巨大的密钥。索引会很难,对吧?数据检索对数据库来说是一个痛苦 - 至少我猜 - 整数键的处理速度要快得多。另一方面,这些也提供了一些安全性:访问者不能通过递增id参数来迭代所有订单或所有用户或所有图片。
C)坚持使用auto_incremented keys? 好的,如果那么,上面例子中描述的交易呢?我怎么解决这个问题?也许首先插入一个Ghost Row,然后用一个UPDATE + n INSERTs做一个事务?
D)还有什么?
答案 0 :(得分:3)
存储订单时,需要交易,以防止只有一半的产品添加到数据库中。
根据您的数据库和连接器,last-insert-id函数返回的值可能与事务无关。例如,使用MySQL,mysql_insert_id
返回来自该特定客户端的最后一个查询的标识符(不受其他客户端同时执行的操作的影响)。
答案 1 :(得分:2)
您使用的是哪个数据库?
是的,通常插入记录然后再次尝试选择它以查找自动生成的密钥是不好的,特别是如果您使用表查询中的天真选择max(id)。这是因为一旦两个线程创建记录,max(id)可能实际上不会返回当前线程使用的最后一个ID。
避免这种情况的一种方法是在数据库中创建序列。从您的代码中选择sequence.NextValue然后使用该值然后执行插入(或者您可以创建一个更复杂的SQL语句来执行此选择并一次性插入)。序列是原子/线程安全的。
在MySQL中,你可以从执行结果中询问最后一个插入的id,我相信它总会给你正确的答案。
答案 2 :(得分:1)
Sql Server支持SCOPE_IDENTITY (Transact-SQL),它应该处理您的事务问题和并发问题。
我会坚持使用 auto_increment 。
答案 3 :(得分:1)
(假设您使用的是MySQL)
“向数据库询问最后一次插入的id(这对于并发是危险的,对吗?)”
如果使用MySQLs last_insert_id()函数,则只能看到会话中发生的事情。所以这是安全的。你提到:
db.last_insert_id()
我不知道它是什么框架或语言,但我认为它使用MySQL的last_insert_id()(如果没有,它是一个非常无用的数据库抽象fromework)
“我相信这会阻止我在几乎所有INSERT案例中使用交易”
我不明白为什么。请解释一下。
答案 4 :(得分:1)
答案 5 :(得分:1)
这个问题没有最终和一般的答案。
添加新记录时,自动递增列很容易使用。要在同一个事务中将它们用作外键,它们就不那么直接了。您需要特定于数据库的命令才能获取新创建的密钥。这种技术对于某些数据库很常见,例如sql server。
序列似乎更难使用,因为您需要在插入行之前获取密钥,但最后它更容易将它们用作外键。这种技术对某些数据库很常见,例如oracle。
当您使用 Hibernate或NHibernate 时,不鼓励使用自动递增键,因为某些优化不再可行。建议使用使用附加表的hi-lo算法。
Guids 很强大,例如在不同数据库,系统,断开连接的场景,导入/导出等之间共享数据时。在许多数据库中,大多数表只包含几百条记录,所以内存和表现不是这样的问题。当使用NHibernate时,你会得到一个生成顺序guid 的guid生成器,因为有些数据库在按键顺序时表现更好。