首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >春季事务原子性

春季事务原子性
EN

Stack Overflow用户
提问于 2016-01-06 14:57:26
回答 4查看 6.5K关注 0票数 11

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

我在想下面这个案子。如果我错了,请纠正我:

此代码不正确:

代码语言:javascript
复制
@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在许多机器上/在许多线程中被并发调用?如果是这样的话,这意味着一次只能执行一个事务,导致效率下降(特别是如果voteUp代码在事务中做了更多的事情)?

唯一正确的方法是这样做:

代码语言:javascript
复制
/* not necessarily */ @Transactional 
public void voteUp(long fooId) {
    fooMapper.voteUp(fooId); // UPDATE foo SET votes = votes + 1 WHERE fooId == #{fooId}
}

在示例中,我使用myBatis连接数据库,但我认为如果使用hibernate或普通SQL语句,问题将保持不变。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2016-01-06 15:51:11

隔离级别决定事务中数据视图的可靠性。最可靠的隔离级别是可序列化的(这会影响数据库的性能),但通常的默认情况是读-提交

在此隔离级别中,基于锁的并发控制DBMS实现将写锁(在所选数据上获取)保持到事务结束为止,但一旦执行SELECT操作,读取锁就会释放(因此,在此隔离级别中可能会出现不可重复的读取现象,如下文所述)。与以前的级别一样,范围锁不被管理. 用简单的话来说,read是一个隔离级别,它保证读取的任何数据在读取时都被提交。它只会限制读者看到任何中间的、未提交的、“脏”的阅读。它没有承诺,如果事务重新发布读取,它将找到相同的数据;数据在读取后可以自由更改。

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

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

票数 9
EN

Stack Overflow用户

发布于 2016-01-06 17:37:39

在这种情况下,@Transactional用于管理SQL事务,它不增加任何线程安全。Spring事务管理器除了要求数据库启动一个新事务外,实际上做的并不多,所以您需要参考RDBMS的文档并阅读它的事务语义。

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

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

乐观锁定:乐观锁定实际上不使用任何锁。您要做的是使用一个值,当该行被更新时,该值肯定会更改。例如,可以将update语句重写到:

代码语言:javascript
复制
UPDATE foo SET votes = #{votes} (...) WHERE fooId == #{fooId} AND votes = #{oldNoOfVotes}

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

票数 4
EN

Stack Overflow用户

发布于 2016-01-06 16:30:28

根据文档的说法,当您用@Transactional对一个方法进行注释时,Spring会创建一个与带注释的类具有相同接口的代理。当调用对象的方法时,所有调用都通过代理对象传递。Proxy对象包装类的事务性方法,在try构造中。您的原始对象的代码:

代码语言:javascript
复制
@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}
}

在代理对象中,如下所示:

代码语言:javascript
复制
//It's all approximately just to show you a way how Spring does it. 
public void voteUp(long fooId) {
    EntityTransaction tx = em.getTransaction();
    tx.begin();
    try{
       originalObject.voteUp(fooId);
       tx.commit();
    }catch(Exception e){
       tx.rallback();
       throw e;
    }
 }

因此,即使在许多机器上/在许多线程中并发调用voteUp,“选票”的值也总是会增加一个。因为一个线程中的事务将阻塞表,以便从其他线程写入数据。

您是对的:如果voteUp方法需要很长时间,它将导致效率下降。这意味着您的方法(由@Transactional注释)不需要花费很长时间。

如果您的ORM库允许这样的话,您可以在没有选择的情况下更新数据库记录。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34636151

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档