9

在 MS Sql Server 中很容易创建自动增量字段。在我的系统中,我停止对主键使用自动增量字段,现在我使用 Guid。太棒了,这种变化给我带来了很多好处。但在另一个非主键字段中,我确实需要实现“软自动增量”。这是因为我的系统是独立于数据库的,所以我在 c# 中以编程方式创建了 autoinc 值。

我想要关于没有自动增量的数据库上的自动增量字段的解决方案,您使用什么解决方案以及为什么?有一些关于这个的 Sql Ansi 声明?并直接从我的 c# 生成,是更好的解决方案吗?

PS:我知道select max(id)+1 from table它并不是真正的并发友好...

4

5 回答 5

16

生成唯一 id 值的机制必须不受事务隔离的约束。这是数据库为每个客户端生成不同值所必需的,这比 的技巧要好,如果两个客户端尝试同时分配新值SELECT MAX(id)+1 FROM table,则会导致竞争条件。id

您无法使用标准 SQL 查询来模拟此操作(除非您使用表锁或可序列化事务)。它必须是一种内置于数据库引擎中的机制。

ANSI SQL 直到 SQL:2003 才描述为代理键生成唯一值的操作。在此之前,自增列没有标准,因此几乎每个品牌的 RDBMS 都提供了一些专有的解决方案。当然,它们变化很大,并且没有办法以简单、独立于数据库的方式使用它们。

  • MySQL 有AUTO_INCREMENTcolumn 选项或SERIAL伪数据类型,相当于BIGINT UNSIGNED AUTO_INCREMENT;
  • Microsoft SQL Server 具有IDENTITY列选项,NEWSEQUENTIALID()介于自动增量和 GUID 之间;
  • Oracle有一个SEQUENCE对象;
  • PostgreSQL 有一个SEQUENCE对象或SERIAL伪数据类型,它根据命名约定隐式创建一个序列对象;
  • InterBase/Firebird 有一个GENERATOR很像SEQUENCEOracle 中的对象;Firebird 2.1SEQUENCE也支持;
  • SQLite 将任何声明为主键的整数视为隐式自动递增;
  • DB2 UDB 几乎拥有一切:对象,或者您可以使用 " " 选项SEQUENCE声明列。GEN_ID

所有这些机制都在事务隔离之外运行,确保并发客户端获得唯一值。此外,在所有情况下,都有一种方法可以查询当前会话最近生成的值。必须有,因此您可以使用它在子表中插入行。

于 2009-02-09T02:43:39.337 回答
2

我认为你的问题实际上是一个很好的问题。但是,尝试提出仅 SQL 的解决方案很容易迷失方向。实际上,您将希望通过使用自动增量类型的数据库实现来提供优化和事务安全。

如果您需要抽象出自动增量运算符的实现,为什么不创建一个存储过程来返回您的自动增量值。大多数 SQL 方言以相对相同的方式访问存储过程,并且它应该更具可移植性。然后,您可以在创建存储过程时创建特定于数据库的自动增量逻辑 - 无需将许多语句更改为特定于供应商。

通过这种方式,您的插入可以很简单:

INSERT INTO foo (id, name, rank, serial_number)
 VALUES (getNextFooId(), 'bar', 'fooRank', 123456);

然后在初始化数据库时以数据库特定的方式定义 getNextFooId() 。

于 2009-02-10T09:51:08.973 回答
1

大多数没有自动增量字段的数据库,如 SQL Server(我特别考虑 Oracle)都有序列,您可以在其中向序列询问下一个数字。无论有多少人同时请求号码,每个人都会得到一个唯一的号码。

于 2009-02-08T21:42:51.323 回答
1

传统的解决方案是有一个看起来像这样的 id 表

CREATE TABLE ids (
  tablename VARCHAR(32) NOT NULL PRIMARY KEY,
  nextid INTEGER
)

创建数据库时,每个表填充一行。

然后,您进行选择以获取要插入的表的下一个 id,将其递增,然后使用新 id 更新表。显然,这里存在锁定问题,但对于插入率适中的数据库,它运行良好。它是完全便携的。

于 2009-02-08T21:47:35.540 回答
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;
于 2011-02-07T12:25:27.930 回答