Spring中的事务原子性

Ars*_*sen 6 java sql spring transactions

SQL/Spring中的事务原子性意味着什么,它意味着什么?

我在考虑以下情况.如果我错了,请纠正我:

此代码不正确:

@Transactional
public void voteUp(long fooId) {
    Foo foo = fooMapper.select(fooId); // SELECT * FROM foo WHERE fooId == #{fooId}
    foo.setVotes(foo.getVotes() + 1);
    fooMapper.update(foo); // UPDATE foo SET votes = #{votes} (...) WHERE fooId == #{fooId}
}
Run Code Online (Sandbox Code Playgroud)

即使它的事务处理并不意味着"投票"的值总是会增加1,如果在很多机器/许多线程中同时调用voteUp?如果它是这样的话,那意味着一次只能执行一个事务,导致效率下降(特别是如果voteUp代码在事务中做了更多的东西)?

唯一正确的方法就是这样(?):

/* not necessarily */ @Transactional 
public void voteUp(long fooId) {
    fooMapper.voteUp(fooId); // UPDATE foo SET votes = votes + 1 WHERE fooId == #{fooId}
}
Run Code Online (Sandbox Code Playgroud)

在示例中,我使用myBatis连接到数据库,但我认为如果我使用hibernate或纯SQL语句,问题就会保持不变.

Nat*_*hes 5

隔离级别确定数据视图在事务中的可靠性.最可靠的隔离级别是可序列化的(这确实会影响数据库的性能),但通常的默认值是read-committed:

在此隔离级别中,基于锁的并发控制DBMS实现保持写锁(在所选数据上获取)直到事务结束,但是一旦执行SELECT操作就会释放读锁(因此不可重复读取现象可以在这种隔离级别发生,如下所述).与上一级别一样,不管理范围锁定.

换句话说,read committed是一个隔离级别,它保证读取时提交任何数据.它只是限制读者看到任何中间的,未提交的,"脏"的读.它没有任何承诺,如果事务重新发出读取,它将找到相同的数据; 数据在读取后可以自由更改.

在第一个示例中,在select和update之间,某些其他进程可以更改计数器的值:选择发生,然后某个其他进程更改计数器的值,然后更新作用于更改的行.

将隔离级别更改为可重复读取应确保第一个示例中的增量正常工作.当然第二个例子是正确的,因为它是一个更好的解决方案.


小智 5

@Transactional在这种情况下用于管理 SQL 事务,它不会添加任何线程安全性。Spring 事务管理器除了要求数据库启动一个新事务之外并没有做太多事情,因此您需要参考 RDBMS 的文档并阅读其事务语义。

所以,是的,即使 SELECT 和 UPDATE 是同一事务的一部分,您的第一个示例中也会存在竞争条件。您的问题有两种可能的解决方案:

1- 行锁定:在要修改的行上获取锁将阻止任何其他 SQL 事务修改其值。

2- 乐观锁:乐观锁实际上不使用任何锁。您所做的就是使用一个您确信每当该行更新时就会发生变化的值。例如,您可以将更新语句重写为:

UPDATE foo SET votes = #{votes} (...) WHERE fooId == #{fooId} AND votes = #{oldNoOfVotes}
Run Code Online (Sandbox Code Playgroud)

如果没有更新任何行,则意味着另一个进程已经更改了该行的值,然后您可以重试或引发异常。