数据库事务的陷阱

本贴最后更新于 4374 天前,其中的信息可能已经物是人非

    写几个例子, 来示范一下 数据库事务 的误区。

    下面的例子场景为转账,使用mysql,innodb, 缺省read committed隔离级别,spring+hibernate.  不考虑边界条件,如余额不足等。

    我只要声明了 事务,就万事大吉了么?

               先给一个entity的代码   

@Entity
public class Money {
    private Long id;
private Long balance; private long version; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Long getId() { return id; } public void setId(Long uid) { this.id = uid; } public Long getBalance() { return balance; } public void setBalance(Long balance) { this.balance = balance; }

}

   在给一个service的代码,一切从简,没有dao.

   

@Service
public class MoneyService {
    @Autowired
    private HibernateTemplate template;
@Transactional public void transfer(Long from, Long to, Long amount) { Money fromAcount = template.load(Money.class, from); Money toAccount = template.load(Money.class, to); fromAcount.setBalance(fromAcount.getBalance() - amount); // just for test try { Thread.sleep(1000l); } catch (InterruptedException e) { e.printStackTrace(); } // //////////////// toAccount.setBalance(toAccount.getBalance() + amount); template.save(fromAcount); template.save(toAccount); }

}

 你觉得这段代码有问题么?同时起2个线程,一个让用户A(余额100)给C(余额100)转账10,另一个让用户B(余额100)给用户C(余额100)转账10, 结果是A(余额90),B(余额90), C(余额110), 丢失了10块钱。。。。。。

  这里面可能引发另外一个疑问,数据库教程书也是这么用的啊,设了事务,人家转账咋就没问题。

  好吧,让我们把数据库教程里的sql代码翻译成同样的hibernate代码。

@Transactional
    public void transfer2(final Long from, final Long to, final Long amount) {
template.execute(new HibernateCallback&lt;Void&gt;() { public Void doInHibernate(Session session) throws HibernateException, SQLException { SQLQuery q = session .createSQLQuery(&quot;update money set balance = (balance - ?) where id = ?&quot;); q.setLong(0, amount); q.setLong(1, from); q.executeUpdate(); return null; } }); // just for test try { Thread.sleep(1000l); } catch (InterruptedException e) { e.printStackTrace(); } // //////////////// template.execute(new HibernateCallback&lt;Void&gt;() { public Void doInHibernate(Session session) throws HibernateException, SQLException { SQLQuery q = session .createSQLQuery(&quot;update money set balance = (balance + ?) where id = ?&quot;); q.setLong(0, amount); q.setLong(1, to); q.executeUpdate(); return null; } }); }</pre>

     这里需要指出,事务保证的只是sql的完整性,如果是程序搞砸的,那事务也爱莫能助。

     transfer产生的sql代码为

            select * from money where id=A.id;

            select * from money where id=C.id;

            update money set balance=90 where id=A.id;

            update money set balance=110 where id=C.id;

     和

            select * from money where id=B.id;

            select * from money where id=C.id;

            update money set balance=90 where id=B.id;

            update money set balance=110 where id=C.id;

      余额90 和 110 都是程序计算出来的,所以事务也爱莫能助。

      而transfer2 产生的sql代码为

            update money set balance=balance - 10 where id=A.id;

            update money set balance=balance + 10 where id=C.id;

      和
            update money set balance=balance - 10 where id=B.id;

            update money set balance=balance + 10 where id=C.id;

      数据库事务保证结果是A(余额90),B(余额90), C(余额120)。

 
      问题虽然解决了,但不能只能写sql啊,我还是需要使用hibernate,怎么办呢?
      这里需要借助悲观锁或乐观锁了。
 
      悲观锁:
    @Transactional
    public void transfer3(Long from, Long to, Long amount) {
        Money fromAcount = template.load(Money.class, from,
                LockMode.PESSIMISTIC_WRITE);
        Money toAccount = template.load(Money.class, to,
                LockMode.PESSIMISTIC_WRITE);
fromAcount.setBalance(fromAcount.getBalance() - amount); // //////////////// try { Thread.sleep(1000l); } catch (InterruptedException e) { e.printStackTrace(); } // //////////////// toAccount.setBalance(toAccount.getBalance() + amount); template.save(fromAcount); template.save(toAccount); }

      这样在进行select的时候,会同时将记录锁住,另外一个线程在对同一条记录进行查询的时候,必须等待锁的释放。需要小心使用悲观锁,不正确的顺序会大大增加数据库死锁的概率。

      结果,余额对了, 但你会发现 其中一个线程的 select 花费了 1秒钟,因为它一直在等待 另外一个线程释放锁。

 

       还有一种解决方案是乐观锁:

       列一下代码:

       

package com.wuxudong.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Version;

@Entity
public class Money {
private Long id;

private Long balance; private long version; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Long getId() { return id; } public void setId(Long uid) { this.id = uid; } public Long getBalance() { return balance; } public void setBalance(Long balance) { this.balance = balance; } @Version public long getVersion() { return version; } public void setVersion(long version) { this.version = version; }

}

  

 @Transactional
 public void transfer4(Long from, Long to, Long amount) {
        Money fromAcount = template
                .load(Money.class, from, LockMode.OPTIMISTIC);
        Money toAccount = template.load(Money.class, to, LockMode.OPTIMISTIC);
fromAcount.setBalance(fromAcount.getBalance() - amount); // //////////////// try { Thread.sleep(1000l); } catch (InterruptedException e) { e.printStackTrace(); } // //////////////// toAccount.setBalance(toAccount.getBalance() + amount); template.save(fromAcount); template.save(toAccount);

}

   换成乐观锁后,一个线程的事务顺利执行成功, 而另一个线程则抛出了乐观锁异常回滚掉了。

  • 数据库

    据说 99% 的性能瓶颈都在数据库。

    345 引用 • 742 回帖 • 1 关注
  • 事务
    23 引用 • 21 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 深度学习

    深度学习(Deep Learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。

    53 引用 • 40 回帖
  • Windows

    Microsoft Windows 是美国微软公司研发的一套操作系统,它问世于 1985 年,起初仅仅是 Microsoft-DOS 模拟环境,后续的系统版本由于微软不断的更新升级,不但易用,也慢慢的成为家家户户人们最喜爱的操作系统。

    227 引用 • 476 回帖
  • Kafka

    Kafka 是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是现代系统中许多功能的基础。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。

    36 引用 • 35 回帖 • 4 关注
  • webpack

    webpack 是一个用于前端开发的模块加载器和打包工具,它能把各种资源,例如 JS、CSS(less/sass)、图片等都作为模块来使用和处理。

    42 引用 • 130 回帖 • 250 关注
  • HHKB

    HHKB 是富士通的 Happy Hacking 系列电容键盘。电容键盘即无接点静电电容式键盘(Capacitive Keyboard)。

    5 引用 • 74 回帖 • 504 关注
  • SVN

    SVN 是 Subversion 的简称,是一个开放源代码的版本控制系统,相较于 RCS、CVS,它采用了分支管理系统,它的设计目标就是取代 CVS。

    29 引用 • 98 回帖 • 688 关注
  • SSL

    SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS 与 SSL 在传输层对网络连接进行加密。

    70 引用 • 193 回帖 • 412 关注
  • Caddy

    Caddy 是一款默认自动启用 HTTPS 的 HTTP/2 Web 服务器。

    12 引用 • 54 回帖 • 166 关注
  • 生活

    生活是指人类生存过程中的各项活动的总和,范畴较广,一般指为幸福的意义而存在。生活实际上是对人生的一种诠释。生活包括人类在社会中与自己息息相关的日常活动和心理影射。

    230 引用 • 1454 回帖
  • WordPress

    WordPress 是一个使用 PHP 语言开发的博客平台,用户可以在支持 PHP 和 MySQL 数据库的服务器上架设自己的博客。也可以把 WordPress 当作一个内容管理系统(CMS)来使用。WordPress 是一个免费的开源项目,在 GNU 通用公共许可证(GPLv2)下授权发布。

    66 引用 • 114 回帖 • 194 关注
  • Typecho

    Typecho 是一款博客程序,它在 GPLv2 许可证下发行,基于 PHP 构建,可以运行在各种平台上,支持多种数据库(MySQL、PostgreSQL、SQLite)。

    12 引用 • 67 回帖 • 447 关注
  • jsoup

    jsoup 是一款 Java 的 HTML 解析器,可直接解析某个 URL 地址、HTML 文本内容。它提供了一套非常省力的 API,可通过 DOM,CSS 以及类似于 jQuery 的操作方法来取出和操作数据。

    6 引用 • 1 回帖 • 488 关注
  • 电影

    这是一个不能说的秘密。

    122 引用 • 608 回帖
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 23 关注
  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3455 回帖 • 165 关注
  • V2EX

    V2EX 是创意工作者们的社区。这里目前汇聚了超过 400,000 名主要来自互联网行业、游戏行业和媒体行业的创意工作者。V2EX 希望能够成为创意工作者们的生活和事业的一部分。

    16 引用 • 236 回帖 • 267 关注
  • TextBundle

    TextBundle 文件格式旨在应用程序之间交换 Markdown 或 Fountain 之类的纯文本文件时,提供更无缝的用户体验。

    1 引用 • 2 回帖 • 83 关注
  • CAP

    CAP 指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。

    12 引用 • 5 回帖 • 636 关注
  • Follow
    4 引用 • 12 回帖 • 10 关注
  • 音乐

    你听到信仰的声音了么?

    62 引用 • 512 回帖
  • Sphinx

    Sphinx 是一个基于 SQL 的全文检索引擎,可以结合 MySQL、PostgreSQL 做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。

    1 引用 • 221 关注
  • Quicker

    Quicker 您的指尖工具箱!操作更少,收获更多!

    36 引用 • 155 回帖
  • 印象笔记
    3 引用 • 16 回帖
  • Netty

    Netty 是一个基于 NIO 的客户端-服务器编程框架,使用 Netty 可以让你快速、简单地开发出一个可维护、高性能的网络应用,例如实现了某种协议的客户、服务端应用。

    49 引用 • 33 回帖 • 35 关注
  • OpenShift

    红帽提供的 PaaS 云,支持多种编程语言,为开发人员提供了更为灵活的框架、存储选择。

    14 引用 • 20 回帖 • 655 关注
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 608 关注
  • Angular

    AngularAngularJS 的新版本。

    26 引用 • 66 回帖 • 545 关注