3

我已经了解了一些关于数据库完整性的知识,并且知道如果我“需要将多个语句作为一个单元执行以使数据保持一致状态”,​​我应该使用事务。应用程序开发人员犯的数据库开发错误(第 16 点,选择答案)

维基百科使用了这个例子:

  1. 借记 100 美元到杂货费用帐户
  2. 将 100 美元存入支票账户

如果我尝试记入一个不存在的帐户 ID,并且我正确使用了约束,则会引发异常,我可以捕获它并回滚。如果停电,这两个更改保证是原子的。


但是,如果我理解正确,事务本身不会在所有情况下帮助我:(以 PHP 和 MySQL 为例)

  1. MySQL:开始事务
  2. MySQL:从表中选择数据
  3. PHP:使用所选数据计算状态
    • PHP:如果状态有效,插入数据
    • PHP:否则,不要插入数据
  4. MySQL:提交事务

这是行不通的,因为查询可以原子地一起执行而不会失败(是 PHP 决定存在错误,而不是某些 SQL 约束)。

其次,我刚刚测试过,事务是同步提交的,但可以异步启动。如果我启动一个事务,并添加 10 秒的延迟,我可以启动慢速脚本,并在这段时间内启动并提交另一个事务,演示并发事务。两个实例可以选择相同的数据,然后才能看到对方的修改。只有修改才能保证是原子的。


那么我能做什么呢?我想锁定一张桌子是可行的,但这是一种好的做法吗?有些条件可以用 SQL 在一条语句中描述,但更复杂的条件不能。

4

1 回答 1

2

这是一个很好的问题。说明你已经考虑了一点。

您描述的问题存在,因为数据库不知道您的数据依赖关系。对于数据库,您的代码选择一些数据并写入一些数据。它不知道您只是根据所选数据写入该数据。通常,您需要告诉数据库您的数据依赖关系。这在每个数据库中都是不同的。

你提到了 MySQL。InnoDB 支持SELECT ... FOR UPDATE。这将为资源发出锁定,以便其他查询无法访问该资源(取决于事务隔离级别)。如果它们锁定相同的资源,这将使您的示例中的第二个事务在第一个事务提交之前无法执行。它锁定哪些资源取决于数据库。

让我们看一个例子。要锁定行,您将首先创建一个事务并使用以下内容查询数据库:

select * from tableA where value > 50 for update

此选择将锁定这些行,以便阻止不兼容的锁。然后你可以在 PHP 中进行处理。准备好后,您可以将行插入另一个表:

insert into tableB values ('some value')

此时,在您提交之前,所有这些行都将被锁定。这些行中的任何一个都对其他客户端不可用。因此,在您的整个事务中,没有其他客户端能够读取您接触过的任何行,除非它们读取未提交。要在您的示例中使用此功能,您只需确保 2 中的所有选择语句都使用 select 进行更新。

另一种方法是在更新语句上告诉数据库。当您发出更新语句时,您还指定您认为数据应该是什么。如果数据库确实更新了某些行,那么您可以确定没有其他任何东西改变了您的数据。如果您没有更新预期的行数,您可以知道其他人更改了您的数据,您应该处理该异常。这是一种乐观并发,您猜测可能没有人会更新您的数据,因此您进行更改。之后,您可以检查是否有人真的这样做了。

查询将类似于:

select value from table where id = '1'

然后稍后:

update table set value = 'new value' where id = '1' and value = 'old value'

其他数据库为您提供了这两个基本思想的其他选择。例如,在乐观模型上,您可以验证时间戳(或自动增量)值而不是实际值。

于 2013-07-22T20:23:15.067 回答