【问题标题】:how to lock a DB table or a range of rows for writing?如何锁定数据库表或一系列行进行写入?
【发布时间】:2011-05-08 12:03:08
【问题描述】:

我有一个带有主键的简单表。大多数读取操作通过键的确切值获取一行。

每一行中的数据在键顺序中与它之前和之后的行保持某种关系。所以当我插入一个新行时,我需要读取它要进入的 2 行,进行一些计算然后插入。

显然,问题在于另一个连接可能会在同一时间间隔内添加具有键值的行。如果它与第二次插入的键值完全相同,我会被覆盖,但如果键值不同但在相同的时间间隔内,则关系可能会被破坏。

解决方案似乎是在我决定添加新行时锁定整个表以进行写入,或者(如果可能的话,我怀疑)锁定键值的间隔。但我更希望那时只读事务不会被阻止。

我在客户端程序和 IBM DB2 免费版中使用带有 libodbc++ wrapper for C++ 的 ODBC(尽管 DB 选择可能仍会改变)。这就是我的想法:

  • 以自动提交和默认隔离模式启动连接
  • 当需要添加新行时,将 auto-commit 设置为 false 并将隔离模式设置为 serialized
  • 读取新键值前后的行
  • 计算并插入新行
  • 提交
  • 返回自动提交和默认隔离模式

这能完成这项工作吗?是否允许其他事务同时读取?还有其他/更好的方法吗?

顺便说一句,我在 libodbc++ i/f 中没有看到指定只读事务的方法。在odbc中可以吗?

编辑:感谢非常有用的答案,我很难选择一个。

【问题讨论】:

    标签: database odbc read-write transaction-isolation table-locking


    【解决方案1】:

    如果您的数据库处于 SERIALIZABLE 模式,则根本不会有任何问题。给定一个键 K,要获取上一个和下一个键,您必须运行以下查询:

    select key from keys where key > K order by key limit 1;      # M?
    select key from keys where key < K order by key desc limit 1; # I?
    

    以上在 MySQL 中有效。此等效查询在 DB2 中有效(来自 cmets):

    select key from keys where key = (select min(key) from keys where key > K);
    select key from keys where key = (select max(key) from keys where key < K);
    

    第一个查询设置一个范围锁,防止其他事务插入大于K且小于或等于M的键。

    第二个查询设置一个范围锁,防止其他事务插入小于 K 且大于或等于 I 的键。

    主键上的唯一索引可防止 K 被插入两次。所以你完全被覆盖了。

    这就是交易的意义所在;这样您就可以像整个数据库被锁定一样编写代码。

    注意:这需要一个支持真正可串行化的数据库。幸运的是,DB2 做到了。其他支持真正可串行化的 DBMS:SQLServer 和 MySQL/InnoDB。不支持的 DBMS:Oracle、PostgreSQL!

    【讨论】:

    • 什么是“真正的可序列化”?
    • 谢谢。不幸的是,DB2 不支持LIMIT 1。我使用ORDER BY 并占据第一行。它会锁定整个桌子吗?我想把它改成SELECT * from mytable WHERE key=(SELECT max(key) FROM mytable where key&lt;K。从锁定的角度来看,它的行为会有所不同吗?
    • 刚刚修复了我的查询;忘记了 ORDER BY 子句。 '''SELECT max(key) FROM mytable where key
    • select key from keys where key &gt; K order by key fetch first 1 rows only 也应该在 DB2 中工作。
    • @Quassnoi Snapshot isolation is called "serializable" mode in Oracle[2][3][4] and PostgreSQL versions prior to 9.1,[5][6] which may cause confusion with the "real serializability" mode. There are arguments both for and against this decision; what is clear is that users must be aware of the distinction to avoid possible undesired anomalous behavior in their database system logic. 来自Wiki
    【解决方案2】:

    如果您的数据库和存储引擎允许这样做,您应该为您尝试插入的两行发出SELECT FOR UPDATE

    这将与任何并发的SELECT FOR UPDATE 冲突。

    缺点是行锁定1012(插入11)也将阻止选择810(插入9)。

    MySQL中的InnoDB也可以在索引上加一个next-key锁,也就是索引记录的锁和下一条记录的间隙。

    在这种情况下,您只需要在第一行发出 SELECT FOR UPDATE 并在此之前同时插入一行。

    但是,这需要强制索引并在索引上提供range 条件,这取决于您的查询可能会也可能不会。

    【讨论】:

    • 非常感谢!请澄清一下:我想我需要处于“手动提交”模式,对吧?默认隔离模式(已提交读)可以吗?其他SELECT(不用于更新)语句是否能够读取锁定的行?
    • @davka:手动提交,当然。对于InnoDB,默认隔离模式是REPEATABLE READ,这是间隙锁所必需的,如果你不想要它们,任何隔离模式都可以。没有FOR UPDATE 子句的并发语句将能够看到锁定的记录(在SQL Server 中,您需要为此启用SNAPSHOT ISOLATION)。
    • SQL 标准推荐 SERIALIZABLE。在这种情况下,您不需要执行 SELECT FOR UPDATE。
    • @Seun:如果没有SELECT FOR UPDATE,您将如何锁定MVCC 引擎(如OraclePostgreSQL)中的记录?
    • Oracle和PostgreSQL的SERIALIZABLE模式被故意破坏了,所以需要SELECT FOR UPDATE,但是InnoDB也是MVCC引擎,不需要SELECT FOR UPDATE,所以我觉得MVCC不是问题。
    【解决方案3】:

    您的一般方法是正确的。但是您应该使用一个覆盖两行和其间所有可能行的 SELECT 语句。例如:

    SELECT * FROM MYTABLE WHERE PKCOL BETWEEN 6 AND 10
    

    在具有悲观锁定和可序列化事务隔离级别的数据库系统中,此 SELECT 语句应防止插入会更改 SELECT 结果的新行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-09-08
      • 2019-12-23
      • 1970-01-01
      • 2021-07-11
      • 2022-01-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多