1

我有一个主键简单的表。大部分读取操作都是通过密钥的确切值获取一行。如何锁定数据库表或一系列行来写入?

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

显而易见的问题是,同时另一个连接可能会在相同的时间间隔内添加一个带有键值的行。如果它与第二次插入失败的密钥值完全相同,我将予以报道,但如果密钥值不同,但在相同的时间间隔内可能会破坏关系。

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

我在客户端程序和IBM DB2免费版中使用的ODBC与libodbc++ wrapper for C++(尽管数据库选择可能仍会更改)。这就是我想这样做的:

  • 开始在连接自动提交和默认隔离模式
  • 当需要添加一个新行,设置自动提交虚假和隔离模式连载
  • 之前和新的键值后
  • 计算读取行和插入新行
  • 提交
  • 返回到自动提交和默认隔离模式

这会做这份工作吗?是否允许其他交易同时阅读?还有其他更好的方法吗?

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

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

回答

2

如果您的数据库处于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中的工作(从注释):

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!

+1

什么是“真正的可串行性”? – Quassnoi 2010-11-11 22:30:54

+0

谢谢。不幸的是,DB2不支持“LIMIT 1”。我使用'ORDER BY'并取第一行。它会锁定整个桌子吗?我想把它改成'SELECT * from mytable WHERE key =(SELECT max(key)FROM mytable where key davka 2010-11-14 10:44:29

+0

只修复了我的查询;忘记了ORDER BY子句。 '''SELECT max(key)FROM mytable其中键 2010-11-15 01:59:28

1

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

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

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

InnoDB in MySQL还可以对索引锁定一个next-key锁定,即锁定索引记录和下一条记录之间的差距。

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

但是,这需要强制索引并在索引上提供range条件,根据您的查询可能或不可能。

+0

非常感谢!请澄清几点:我想我需要进入“手动提交”模式,对吧?默认隔离模式(Read Committed)对此是否正常?其他'SELECT'(不适用于更新)语句是否能够读取锁定的行? – davka 2010-11-11 14:31:58

+0

@davka:手工提交,绝对。对于'InnoDB',默认隔离模式是'REPEATABLE READ',这是间隙锁定所必需的,如果你不需要它们,任何隔离模式都可以。没有'FOR UPDATE'子句的并发语句将能够看到锁定的记录(在'SQL Server'中,你需要启用'SNAPSHOT ISOLATION')。 – Quassnoi 2010-11-11 14:35:30

+0

虽然SQL标准建议使用SERIALIZABLE。在这种情况下,您不需要执行SELECT FOR UPDATE。 – 2010-11-11 20:39:09

1

您的一般方法是正确的。但是你应该使用一个SELECT语句来覆盖这两行以及它们之间的所有可能的行。例如:

SELECT * FROM MYTABLE WHERE PKCOL BETWEEN 6 AND 10 

与悲观锁和事务隔离级别序列化,这个SELECT语句应防止新的行插入,将改变SELECT的结果数据库系统。

相关问题