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}
}

即使它是事务性的,这并不意味着如果在许多计算机上/在许多线程中同时调用voteUp,“votes”的值将始终增加1。如果是这样,那意味着一次只能执行一个事务,从而导致效率下降(尤其是如果voteUp的代码在事务中包含更多内容)?

唯一正确的方法是这样(?):
/* not necessarily */ @Transactional
public void voteUp(long fooId) {
    fooMapper.voteUp(fooId); // UPDATE foo SET votes = votes + 1 WHERE fooId == #{fooId}
}

在示例中,我使用myBatis来连接数据库,但我认为如果使用休眠或普通SQL语句,问题仍然存在。

最佳答案

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

在此隔离级别中,基于锁的并发控制DBMS实现将写锁(在选定数据上获取)保持到事务结束,但是一旦执行SELECT操作,就会释放读锁(因此出现不可重复的读现象)。可能会在此隔离级别中发生,如下所述)。与上一级别一样,不管理范围锁。
简而言之,读取提交是一种隔离级别,可确保任何读取的数据在读取时即已提交。它只是限制了读者看不到任何中间的,未提交的“脏”读物。它不保证如果事务重新发出读取,它将找到相同的数据。数据在读取后可以自由更改。

在第一个示例中,在选择和更新之间,其他一些过程可以更改计数器的值:发生选择,然后其他一些过程更改计数器的值,然后更新作用于更改的行。
将隔离级别更改为可重复读取,应确保第一个示例中的增量正常工作。当然,第二个例子是正确的,并且是更好的解决方案。

07-24 19:01