我已经学到了很多关于数据库完整性的知识,并知道如果我“需要将多个语句作为一个单元来执行以保持数据处于一致状态”,我应该使用事务。 Database development mistakes made by application developers(第16点,选择回答)
Wikipedia使用示例:
如果我尝试归功于不存在的帐户ID,并且我正在使用约束,则会抛出异常并且我可以捕获它并回滚。如果断电,这两个变化将保证是原子的。
但是,如果我理解正确,在所有情况下,事务本身都无法帮助我:(使用PHP和MySQL的例子)
这不起作用,因为查询可以原子方式一起执行而不会失败(它是PHP决定存在错误,而不是某些SQL约束)。
其次,我刚刚测试过,事务是同步提交的,但可以异步启动。 如果我启动一个事务,并添加10秒的延迟,我可以启动慢速脚本,然后启动并提交另一个事务,演示并发事务。在看到其他实例的修改之前,两个实例可以选择相同的数据。只保证修改是原子的。
那我该怎么办?我想锁定一个表是有效的,但这是一个好习惯吗?某些条件可以在单个语句中用SQL描述,但更复杂的条件不能。
答案 0 :(得分:1)
这是一个很好的问题。表明你一直在考虑它。
您所描述的问题存在是因为数据库不了解您的数据依赖性。对于数据库,您的代码选择一些数据并写入一些数据。它不知道您只是根据所选数据编写该数据。通常,您需要告知数据库有关数据依赖性的信息。这在每个数据库中以不同方式完成。
你提到了MySQL。 InnoDB支持SELECT ... FOR UPDATE。这将为资源发出锁定,以便其他查询无法访问资源(取决于事务隔离级别)。这将使您的示例中的第二个事务在第一个事务提交之前无法执行,如果它们锁定相同的资源。它锁定的资源取决于数据库。
让我们看一个例子。要锁定行,首先要创建一个事务并使用以下内容查询数据库:
select * from tableA where value > 50 for update
此选择将锁定这些行,以便阻止不兼容的锁。然后你可以用PHP进行处理。准备好后,可以将行插入另一个表中:
insert into tableB values ('some value')
此时,在提交之前,所有这些行都将被锁定。这些行都不可用于其他客户端。因此,在整个事务中,除非他们读取未提交的内容,否则任何其他客户端都无法读取您触摸过的任何行。要在您的示例中使用此功能,您只需确保2中的所有select语句都使用select for update。
另一种方法是在update语句中告诉数据库。当您发出更新语句时,您还要指明您认为数据应该是什么。如果数据库确实更新了某些行,那么您可以确定没有其他任何内容更改了您的数据。如果不更新预期的行数,则可以知道其他人已更改了您的数据,您应该处理该异常。这是乐观的并发性,您猜测可能没有人会更新您的数据,因此您可以进行更改。之后,您可以检查是否有人确实这样做了。
查询将如下:
select value from table where id = '1'
然后:
update table set value = 'new value' where id = '1' and value = 'old value'
其他数据库为您提供了这两个基本想法的其他选择。例如,在乐观模型上,您可以验证时间戳(或自动增量)值而不是实际值。