在MS Sql Server中很容易创建自动增量字段。在我的系统中,我停止使用自动增量字段作为主键,现在我使用Guid了。太棒了,这个改变我有很多好处。但在另一个非主要关键领域,我真的需要实现“软自动增量”。这是因为我的系统与数据库无关,所以我在c#中以编程方式创建了autoinc值。
我想了解没有自动增量的数据库中自动增量字段的解决方案,您使用的解决方案是什么?为什么?关于这个有一些Sql Ansi声明吗?直接从我的c#生成,是一个更好的解决方案吗?
PS:我知道从表中选择max(id)+1 它不是真正的并发友好...
答案 0 :(得分:15)
生成唯一ID值的机制不得受到事务隔离。这是数据库为每个客户端生成不同值所必需的,优于SELECT MAX(id)+1 FROM table
的技巧,如果两个客户端同时尝试分配新的id
值,则会导致竞争条件。
您无法使用标准SQL查询来模拟此操作(除非您使用表锁或可序列化事务)。它必须是内置于数据库引擎中的机制。
在SQL:2003之前,ANSI SQL没有描述为代理键生成唯一值的操作。在此之前,没有自动递增列的标准,因此几乎每个品牌的RDBMS都提供了一些专有解决方案。当然它们变化很大,并且没有办法以简单的,与数据库无关的方式使用它们。
AUTO_INCREMENT
列选项,或SERIAL
伪数据类型,相当于BIGINT UNSIGNED AUTO_INCREMENT
; IDENTITY
列选项和NEWSEQUENTIALID()
,它位于自动增量和GUID之间; SEQUENCE
对象; SEQUENCE
对象,或SERIAL
伪数据类型,它根据命名约定隐式创建一个序列对象; GENERATOR
对象,与Oracle中的SEQUENCE
非常相似; Firebird 2.1也支持SEQUENCE
; SEQUENCE
个对象,或者您可以使用“GEN_ID
”选项声明列。所有这些机制都在事务隔离之外运行,确保并发客户端获得唯一值。此外,在所有情况下,都有一种方法可以查询当前会话的最近生成的值 。必须有,因此您可以使用它在子表中插入行。
答案 1 :(得分:2)
我认为你的问题实际上是一个很好的问题。但是,尝试提出仅SQL解决方案很容易迷失。实际上,您将需要通过使用自动增量类型的数据库实现来提供优化和事务安全性。
如果需要抽象出自动增量运算符的实现,为什么不创建一个存储过程来返回自动增量值。大多数SQL方言以相同的方式访问存储过程,并且应该更加可移植。然后,您可以在创建sproc时创建特定于数据库的自动增量逻辑 - 从而无需将许多语句更改为特定于供应商。
通过这种方式,您的插入可以像下面那样简单:
INSERT INTO foo (id, name, rank, serial_number)
VALUES (getNextFooId(), 'bar', 'fooRank', 123456);
然后在初始化数据库时以特定于数据库的方式定义getNextFooId()。
答案 2 :(得分:1)
大多数没有像SQL Server这样的自动增量字段的数据库(我正在考虑专门的Oracle)都有序列,您可以在其中询问序列中的下一个数字。无论有多少人在同一时间请求号码,每个人都会获得一个唯一的号码。
答案 3 :(得分:1)
传统的解决方案是设置一个类似于此的
CREATE TABLE ids (
tablename VARCHAR(32) NOT NULL PRIMARY KEY,
nextid INTEGER
)
在创建数据库时,每个表填充一行。
然后执行select以获取要插入的表的下一个id,增加它,然后使用新id更新表。显然,这里存在锁定问题,但对于具有中等插入率的数据库,它运行良好。它完全便携。
答案 4 :(得分:0)
如果您需要非主键自动增量字段,那么创建任意序列的非常好的MySQL解决方案是使用相对未知的last_insert_id(expr)
函数。
如果expr作为参数给出 LAST_INSERT_ID(),值的 参数由函数返回 并被记为下一个值 由LAST_INSERT_ID()返回。这个 可以用来模拟序列...
(来自http://dev.mysql.com/doc/refman/5.1/en/information-functions.html#function_last-insert-id)
这是一个示例,演示如何保留二级序列,以便为每个帖子编写注释:
CREATE TABLE `post` (
`id` INT(10) UNSIGNED NOT NULL,
`title` VARCHAR(100) NOT NULL,
`comment_sequence` INT(10) UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
);
CREATE TABLE `comment` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`post_id` INT(10) UNSIGNED NOT NULL,
`sequence` INT(10) UNSIGNED NOT NULL,
`content` TEXT NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO post(id, title) VALUES(1, 'first post');
INSERT INTO post(id, title) VALUES(2, 'second post');
UPDATE post SET comment_sequence=Last_insert_id(comment_sequence+1) WHERE id=1;
INSERT INTO `comment`(post_id, sequence, content) VALUES(1, Last_insert_id(), 'blah');
UPDATE post SET comment_sequence=Last_insert_id(comment_sequence+1) WHERE id=1;
INSERT INTO `comment`(post_id, sequence, content) VALUES(1, Last_insert_id(), 'foo');
UPDATE post SET comment_sequence=Last_insert_id(comment_sequence+1) WHERE id=1;
INSERT INTO `comment`(post_id, sequence, content) VALUES(1, Last_insert_id(), 'bar');
UPDATE post SET comment_sequence=Last_insert_id(comment_sequence+1) WHERE id=2;
INSERT INTO `comment`(post_id, sequence, content) VALUES(2, Last_insert_id(), 'lorem');
UPDATE post SET comment_sequence=Last_insert_id(comment_sequence+1) WHERE id=2;
INSERT INTO `comment`(post_id, sequence, content) VALUES(2, Last_insert_id(), 'ipsum');
SELECT * FROM post;
SELECT * FROM comment;