探秘数据库中的事务

本文包括:

1、事务概念

2、MySQL管理事务

3、JDBC控制事务进程

4、事务的特性(ACID)

5、事务的隔离级别

6、事务的丢失更新问题(lost update)

1、事务的概念

  1. 事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。
    例如:A——B转帐,对应于如下两条sql语句

     update from account set money=money-100 where name=‘a’; 
     update from account set money=money+100 where name=‘b’;
    
  2. 自动提交

    • MySQL 数据库 默认情况下 一条SQL就是一个单独事务,事务是自动提交的

    • Oracle 数据库 默认情况下 事务不是自动提交 ,所有SQL都将处于一个事务中,你需要手动进行commit提交/rollback回滚

2、MySQL管理事务

  1. 在事务管理中执行sql,使用数据库内临时表保存,在没有进行事务提交或者回滚,其它用户无法看到事务操作结果的

  2. SQL语言中只有DML才能被事务管理,如insert update delete

  3. SQL语句:

    • start transaction 开启事务 (所有对数据表增加、修改、删除操作 临时表进行)

    • rollback 回滚事务 (取消刚刚操作)

    • commit 提交事务 (确认刚才操作)

    • savepoint my_savepoint 保存点(回滚点,像打游戏时的存档)

    • rollback to savepoint my_savepoint 回滚到指定的保存点

3、JDBC控制事务进程

  1. 当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行。

  2. JDBC控制事务语句:

     Connection.setAutoCommit(false); //  相当于SQL语句的start transaction
     
     Connection.rollback();  // 相当于SQL语句的rollback
     
     Connection.commit();  // 相当于SQL语句的commit
    
     Savepoint sp = conn.setSavepoint(); // 相当于SQL语句的savepoint my_savepoint
    
     Conn.rollback(sp); // 相当于SQL语句的rollback to savepoint my_savepoint
    
  3. 注意:

    • 使用保存点进行事务控制时,回滚到指定保存点之后,必须要调用commit

    • 如果直接使用没有保存点的rollback命令,那么事务中的所有保存点都将被忽略,并且撤销整个事务(这就是普通rollback之后不需要调用commit的原因)

  4. demo:

    其中JDBCUtils文件源码在另外一篇文章《Java与数据库的桥梁——JDBC》

     package cn.itcast.test;
     
     import java.sql.Connection;
     import java.sql.PreparedStatement;
     import java.sql.SQLException;
     import java.sql.Savepoint;
     
     import org.junit.Test;
     
     import cn.itcast.utils.JDBCUtils;
     
     public class TransferTest {
         @Test
         public void demo3() {
             // 创建person表,向表中插入20000条数据 ------ 如果插入过程中发生错误,则回滚到插入数据条数为1000的整数倍的时候
             // PreparedStatement 批处理
             Connection conn = null;
             PreparedStatement stmt = null;
             Savepoint savepoint = null;
     
             try {
                 conn = JDBCUtils.getConnection();
                 // 开启事务
                 conn.setAutoCommit(false);
                 // 保存一次
                 savepoint = conn.setSavepoint();
     
                 String sql = "insert into person values(?,?)";
                 // 预编译SQL
                 stmt = conn.prepareStatement(sql);
     
                 for (int i = 1; i <= 20000; i++) {
                     stmt.setInt(1, i);
                     stmt.setString(2, "name" + i);
     
                     // 添加到批处理
                     stmt.addBatch();
     
                     if (i == 4699) {
                         int d = 1 / 0;
                     }
     
                     // 每隔200向数据库发送一次
                     if (i % 200 == 0) {
                         stmt.executeBatch();
                         stmt.clearBatch();
                     }
     
                     if (i % 1000 == 0) {// 1000整数倍
                         // 保存 回滚点
                         savepoint = conn.setSavepoint();
                     }
                 }
     
                 stmt.executeBatch();// 为了确保缓存sql都提交了
     
                 // 没有错误
                 conn.commit();
     
             } catch (Exception e) {
                 // 回滚事务,回滚存储点
                 try {
                     conn.rollback(savepoint);
                     conn.commit();
                 } catch (SQLException e1) {
                     e1.printStackTrace();
                 }
                 e.printStackTrace();
             } finally {
                 JDBCUtils.release(stmt, conn);
             }
         }
     
         @Test
         public void demo2() {
             // 模拟转账操作,使用事务管理
             Connection conn = null;
             PreparedStatement stmt = null;
     
             try {
                 conn = JDBCUtils.getConnection();
                 // 1、在连接获得后,开启事务 start transaction
                 conn.setAutoCommit(false);// 关闭自动提交
     
                 // 没有用事务管理,两句sql 就是两个事务
                 String sql1 = "update account set money = money - 100 where name='aaa'";
                 String sql2 = "update account set money = money +100 where name ='bbb'";
     
                 stmt = conn.prepareStatement(sql1);
                 stmt.executeUpdate();// 执行 update操作
     
                 // int d = 1 / 0;
     
                 stmt = conn.prepareStatement(sql2);
                 stmt.executeUpdate();// 执行 update操作
     
                 // 2、两句SQL 都执行成功,事务commit
                 System.out.println("事务提交!");
                 conn.commit();
     
             } catch (Exception e) {
                 // 3、在执行转账过程中 发生错误,将两句sql 进行回滚
                 System.out.println("事务回滚!");
                 try {
                     conn.rollback();
                 } catch (SQLException e1) {
                     e1.printStackTrace();
                 }
     
                 e.printStackTrace();
             } finally {
                 JDBCUtils.release(stmt, conn);
             }
         }
     
         @Test
         public void demo1() {
             // 模拟转账操作,先不使用事务管理
             Connection conn = null;
             PreparedStatement stmt = null;
     
             try {
                 conn = JDBCUtils.getConnection();
                 // 没有用事务管理,两句sql 就是两个事务
                 String sql1 = "update account set money = money - 100 where name='aaa'";
                 String sql2 = "update account set money = money +100 where name ='bbb'";
     
                 stmt = conn.prepareStatement(sql1);
                 stmt.executeUpdate();// 执行 update操作
     
                 int d = 1 / 0;
     
                 stmt = conn.prepareStatement(sql2);
                 stmt.executeUpdate();// 执行 update操作
     
             } catch (Exception e) {
                 e.printStackTrace();
             } finally {
                 JDBCUtils.release(stmt, conn);
             }
         }
     }
    

4、事务的特性(ACID)

  • 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

  • 一致性(Consistency)事务前后数据的完整性必须保持一致。

  • 隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。

  • 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

企业开发中一定要保证事务原子性,事务最复杂问题都是由事务隔离性引起的

5、事务的隔离级别

  1. 不考虑事务隔离将引发哪些问题:脏读、不可重复读、虚读

    • 脏读:一个事务读取另一个事务未提交数据(数据库隔离中最重要的问题)

      这是非常危险的,假设A要在B的网店购物,A向B转帐100元,对应sql语句如下所示

         1.update account set money=money+100 while name=‘b’;    
         2.update account set money=money-100 while name=‘a’;
      

      当第1条sql执行完,第2条还没执行(A未提交时),如果此时B查询自己的帐户,就会发现自己多了100元钱,立即发货,如果A等B发货后再回滚,B就会损失货物。

    • 不可重复读:一个事务读取另一个事务未提交和已提交数据,两次读取结果不同(有时并不一定是坏问题)。

      1. 例如银行想查询A帐户余额,第一次查询A帐户为200元,此时A向帐户存了100元并提交了,银行接着又进行了一次查询,此时A帐户为300元了。银行两次查询不一致,可能就会很困惑,不知道哪次查询是准的。
      1. 不可重复读与脏读的区别是:脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据。
    • 虚读:一个事务读取另一个事务插入数据,造成在一个事务中两次读取记录条数不同(比不可重复读危害性更低,很多数据库不认为虚读是问题)。

      1. 如丙存款100元未提交,这时银行做报表统计account表中所有用户的总额为500元,然后丙提交了,这时银行再统计发现帐户为600元了,造成虚读同样会使银行不知所措,到底以哪个为准。
      1. 虚读与不可重复读的区别:不可重复读读取update的数据 ,虚读读取insert的数据。
  2. 数据库共定义了四种隔离级别:

    • Serializable : 可解决 脏读、不可重复读、虚读情况的发生。串行处理 ---解决三类问题

    • Repeatable read :可以解决 不可重复读、脏读,会发生虚读 ------MySQL 默认级别

    • read committed : 可以 解决脏读 ,会发生 不可重复读、虚读(读已提交) -------Oracle默认级别

    • read uncommitted : 最低级别,会导致三类问题发生 (读未提交)

    Serializable > Repeatable read > read committed > read uncommitted

    数据库隔离问题危害:脏读> 不可重复读 > 虚读,

    安全级别越高,处理效率越低;安全级别越低,效率越高。所以一般数据库把默认级别定位中间的两级。

  3. 有关SQL语句:

    • set transaction isolation level XXX; 设置事务隔离级别

    • select @@tx_isolation; 查询当前事务隔离级别

  4. JDBC控制数据库隔离级别:Connection类

     Connection setTransactionIsolation(int level);
    

    level字段摘要:

    • static int TRANSACTION_NONE 指示事务不受支持的常量。
    • static int TRANSACTION_READ_COMMITTED 指示不可以发生脏读的常量;不可重复读和虚读可以发生。
    • static int TRANSACTION_READ_UNCOMMITTED 指示可以发生脏读 (dirty read)、不可重复读和虚读 (phantom read) 的常量。
    • static int TRANSACTION_REPEATABLE_READ 指示不可以发生脏读和不可重复读的常量;虚读可以发生。
    • static int TRANSACTION_SERIALIZABLE 指示不可以发生脏读、不可重复读和虚读的常量。

6、事务的丢失更新问题(lost update)

  1. 丢失更新问题描述:两个事务查询同一行,再先后更新这一行,但这些事务彼此之间都不知道其它事务进行的修改,所以因此第二个更改覆盖了第一个修改。


  2. 首先介绍:在MySQL内部有两种常用锁:读锁和写锁

    在mysql中默认情况下,在事务中当你修改数据,自动为数据加锁,防止两个事务同时修改数据 ---- 即读锁

    事务和锁和不可分开的,锁一定是在事务中使用,当事务关闭锁自动释放。

    • 读锁(共享锁) 一张表可以添加多个读锁,如果表已经添加了读锁(该读锁不是当前事务添加的),则该表不可以修改

      • select * from account lock in share mode;

      • 共享锁非常容易发生死锁(两个事务都为同一张表添加了共享锁,则该表在两个事务中都不能修改)

    • 写锁(排它锁) 一张表只能加一个排它锁,如果表已经添加了写锁,则该表不可以查询,也不可以修改

      • select * from account for update ;

      • 如果一张表想添加排它锁,则之前表一定没有加过共享锁和排他锁,否则无法添加排它锁(排他锁和其它共享锁、排它锁都具有互斥效果)

  1. 丢失更新问题的解决有两种方法:

    • 悲观锁( Pessimistic Locking )

        select * from table lock in share mode(读锁、共享锁)
        select * from table for update (写锁、排它锁)
      
    • 乐观锁( Optimistic Locking )
      通过时间戳字段

  2. 悲观锁原理:使用数据库内部锁机制,进行table的锁定,在A修改数据时,A就将数据锁定,B此时无法进行修改 ----- 无法发生两个事务同时修改

    由1、2点可知,悲观锁可以使用排它锁实现

    为什么叫悲观锁?

    因为假设丢失更新会发生,所以是悲观的。

  3. 乐观锁原理:使用的不是数据库锁机制,而是一个特殊时间戳标记字段,假设两个事务查询了同一个记录,A事务修改了该记录,则时间戳字段会更新,若B事务再修改该记录,此时把之前查询的时间戳字段与当前时间戳字段比较,得知数据是否发生了并发访问。

    表额外增加一个timestamp类型的字段,即时间戳字段,该字段在新增数据时可设为null,然后会自动生成当前时间,同时在修改该记录时,也会自动更新为当前时间。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容