这个INSERT是否可能导致任何锁定/并发问题?

时间:2011-02-05 10:58:28

标签: sql database postgresql relational-database

为了在这个特定的数据库中出于某种原因避免自动序列号等,我想知道是否有人可以看到任何问题:

INSERT INTO user (label, username, password, user_id)
SELECT 'Test', 'test', 'test', COALESCE(MAX(user_id)+1, 1) FROM user;

我正在使用PostgreSQL(但也试图尽可能地与数据库无关)..

修改 我想要做到这一点有两个原因。

  • 保持对任何特定RDBMS的依赖性低。
  • 如果将数据批量更新到中央数据库,则无需担心更新序列。

插入性能不是问题,因为需要它的唯一表是设置表。

修改-2: 我正在玩的想法是数据库中的每个表都有一个人工生成的SiteCode作为其键的一部分,所以我们总是有一个复合键。这有效地划分了SiteCode上的数据,并允许从特定站点获取数据并将其放在其他位置(显然在相同的数据库结构上)。例如,这将允许将各种操作站点备份到一个中央数据库上,但也允许该中央数据库具有使用它的操作站点。 我仍然可以使用序列,但它似乎很乱。实际的INSERT看起来更像是这样:

INSERT INTO user (sitecode, label, username, password, user_id)
SELECT 'SITE001', 'Test', 'test', 'test', COALESCE(MAX(user_id)+1, 1)
FROM user
WHERE sitecode='SITE001';

如果这是有道理的.. 我之前做过类似的事情并且工作正常,但是在这种情况下,中央数据库从不可操作(它更像是一种集中查看数据/分析的方式)所以它不需要生成IDS。

修改-3: 我开始认为只允许集中式数据库为仅活动数据库或仅备份数据库更简单,从而完全避免问题并允许更简单的设计。

哦,回到绘图板!

6 个答案:

答案 0 :(得分:2)

是的,我可以看到巨大的问题。 不要这样做。

多个连接可以同时获得EXACT SAME ID。我打算添加“负载”,但它甚至不需要 - 只需要在两个查询之间的正确时间。

为避免这种情况,您可以使用特定于每个数据库的事务或锁定机制或隔离级别,但是一旦我们进入该阶段,您也可以使用特定于dbms的序列/身份/自动编号等。

修改

对于question edit2,没有理由担心user_id中的间隙,因此您在所有网站上都有一个序列。如果差距没问题,可以选择一些选项

  • 使用有保证的更新语句,例如(在SQL Server中)

    更新tblsitesequenceno set @nextnum = nextnum = nextnum + 1

此语句的多个调用者都可以获得唯一的编号。

  • 使用生成identity / sequence / autonumber(db specific)的单个表

如果根本没有间隙,请考虑使用在运行max()查询时限制访问的事务机制。要么是使用动态SQL,要么使用相同技术对单个序列进行操作,要么扩散(带有自动编号的标识列/表的序列/表)。

答案 1 :(得分:2)

有几点:

  • Postgres使用多版本并发控制(MVCC),因此读者永远不会等待作者,反之亦然。但是每次写入时都会发生序列化。如果要将大量数据加载到系统中,请查看COPY命令。它比运行大量INSERT语句快得多。
  • MAX(user_id)可以用索引来回答,如果user_id列上有索引,则可能是。但真正的问题是,如果两个事务同时开始,它们将看到相同的MAX(user_id)值。它引导我到下一点:
  • 处理像user_id这样的数字的规范方法是使用SEQUENCE。这些基本上是您可以从中绘制下一个用户ID的地方。如果您真的担心生成下一个序列号的性能,您可以为每个线程生成一批它们,然后在耗尽时请求新批处理(有时称为HiLo序列)。
  • 你可能想要让user_id的包装好起来,因为数字越来越多,但我觉得你应该试着摆脱它。原因是删除user_id无论如何都会创建一个漏洞。如果序列没有严格增加,我不会太担心。

答案 2 :(得分:2)

一定要使用序列来生成唯一的数字。它们快速,交易安全可靠。

“序列生成器”的任何自编写实现都不能针对多用户环境进行扩展(因为您需要进行大量锁定)或者只是不正确。

如果你确实需要独立于DBMS,那么创建一个抽象层,使用那些支持它们的DBMS(Posgres,Oracle,Firebird,DB2,Ingres,Informix ......)和一个自编写的生成器。不这样做。

尝试创建一个独立于DBMS的系统,只是意味着如果不利用每个DBMS的优势,它将在所有系统上运行同样慢。

答案 3 :(得分:1)

你的目标很好。避免使用IDENTITY和AUTOINCREMENT列意味着避免过多的管理问题。这里只有one example

  • 然而,SO的大多数响应者都不会欣赏它,流行的(而不是技术性的)响应是“始终坚持Id AUTOINCREMENT列”。

  • 下一个序列号很好,所有供应商都对它进行了优化。

  • 只要此代码在交易中,应该是两个用户获得相同的MAX()+1值。在编码事务时需要理解一个名为隔离级别的概念。

  • 离开user_id并转到更有意义的关键字,例如ShortNameStateUserNo甚至更好(前者扩散了争用,后者避免与高容量系统相关的下一顺序争用。

  • MVCC所承诺的,以及它实际提供的内容,是两回事。只需上网或搜索SO即可查看PostcreSQL / MVCC的数百个问题。在计算机领域,物理定律适用,没有任何东西是免费的。 MVCC存储触及的所有行的私有副本,并解决事务的 end 处的冲突,从而导致更多的Rollback。而2PL阻止了交易的开始,并等待,没有大量的副本存储。

    • MVCC的实际经验的大多数人不推荐用于高争用,高容量系统。

第一个示例代码块很好。

根据评论,此项目不再适用:第二个示例代码块存在问题。 “SITE001”不是复合键,它是复合列。不要那样做,将“SITE”和“001”分成两个不连续的列。如果“SITE”是固定的重复值,则可以将其删除。

答案 4 :(得分:0)

不同的用户可以拥有相同的user_id,并发的SELECT语句将看到相同的MAX(user_id)。

如果您不想使用SEQUENCE,则必须使用带有单个记录的额外表格,并在每次需要新的唯一ID时更新此单个记录:

CREATE TABLE my_sequence(id INT);

BEGIN;
UPDATE my_sequence SET id = COALESCE(id, 0) + 1;
INSERT INTO 
  user (label, username, password, user_id)
SELECT 'Test', 'test', 'test', id FROM my_sequence;
COMMIT;

答案 5 :(得分:0)

我同意maksymko,但不是因为我不喜欢序列或自动递增数字,因为他们有自己的位置。如果您需要一个值在整个“各种操作站点”中是唯一的,即不仅在单个数据库实例的范围内,全局唯一标识符是一个强大而简单的解决方案。