[SQL] 带你搞清SQL中的事务(学习总结)

目录

事务(Transaction)

1.解释:

2.狭义:

3.事务的四个特性:ACID

4.一些区分

5.我们如何使用事务

6.通过事务保护了原子性Atomic:

7.事务的lsolation(隔离性):

7.1 read uncommitted(读未提交)

7.2 read committed(读已提交)

 7.3 repeatable read(可重复读)

7.4 snapshot read(快照读)

7.5 serializeable(可串行性)


事务(Transaction)

1.解释:

这个单词的指向含义的广泛的,并不一定就特指我们数据库中的事务处理

为什么出现事务:维护数据的一致性 + 一个业务动作有多条 SQL 语句组成

2.狭义:

在业务方(开发者)看来,就是一个不可再分的业务动作,这个动作最终表达为一条或者多条SQL语句。

3.事务的四个特性:ACID

Atomic(原子性):

业务动作对应的 SQL 应该是看作一个整体,不可再分的。针对数据的修改只能 All or None。

Consistency(一致性):

业务方来定义的,针对数据整体做的不可变的承诺。

lsolation(隔离性):

当有多个 DBMS 的用户,同时针对数据做增删查改时,是否互相直接保持隔离的特性。最理想的情况下,一个用户在操作数据时,是意识不到其他用户同时也在操作数据的。

Durability(持久性):

一旦 DBMS 通知我们数据修改成功了,则数据必然修改成功了(修改被持久化了)。

4.一些区分

Consistency(最终目标)需要 DBMS 和 我们(程序员)共同付出努力来进行维护的

Atomic(原子性)lsolation(隔离性)Durability(持久性):这三个属于 DBMS 对我们的承诺,属于 DBMS 的责任

DBMS承诺支持事务,指的就是 A、D、I (C 是做不到的)

Atomic(原子性)Durability(持久性):这两个,是单用户场景下的,关于数据存储和恢复问题的

lsolation(隔离性):多用户场景下的,,属于并发控制的内容

5.我们如何使用事务

(我们和 DBMS 之间在进行数据交换)

DBMS 不知道 SQL 是怎么划分成不同的事务的

第一个角度:我们如何在 SQL 中使用事务

        strat transaction; 告诉 DBMS 一个事务明确开启了(多条 SQL 组成的事务)

        执行属于这个事务的 SQL

        commit;   事务执行完成

比如,以上图为例:

start transaction;

sql1;

sql2;

sql3;

commit;   // 代表事务1完成了

sql4;       // 事务中只有一条 sql,所以可以省略分界

start transaction;

sql5;

sql6;

commit;   // 代表事务3完成了

第二个角度:通过 JDBC 的方式来使用事务

4个场景:

1.有事务,commit

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

// 一切正常下的 sql 演示
public class Demo1 {
    public static void main(String[] args) throws SQLException {
        String sql1 = "insert into records (rid, bid) values (1, 2)";
        String sql2 = "update books set count = count - 1 where bid = 2";

        // 要使用事务,在同一个事务中,操作 sql1 和 sql2,意味着必须在一条 Connection 完成
        try(Connection c = DBUtil.connection()){
            // connection 中有一个 自动提交(autocommit)的属性,默认情况下,是 true(开启)
            // 开启状态下,意味着,每一条 sql 都会被独立的视为一个事务
            // 我们要让 sql1 和 sql2 看作整体,只需要关闭 connection 的自动提交
            c.setAutoCommit(false);     // 不会提交,只能被我们进行手动提交
                                        // 我们就可以手动的控制事务的结束位置
            try(PreparedStatement ps = c.prepareStatement(sql1)){
                ps.executeUpdate();
            }
            try(PreparedStatement ps = c.prepareStatement(sql2)){
                ps.executeUpdate();
            }

            // 由于我们关闭了自动提交,所以,所修改的还没有真正的落盘
            c.commit();    // 只有加上这句,事务才被真正的提交了
        }

    }
}

2.没有事务,被动失败(重启服务器)

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

// 没有事务,被动失败(重启服务器)
public class Demo2 {
    public static void main(String[] args) throws SQLException {
        String sql1 = "insert into records (rid, bid) values (1, 2)";
        String sql2 = "update books set count = count - 1 where bid = 2";

        try(Connection c = DBUtil.connection()){

            try(PreparedStatement ps = c.prepareStatement(sql1)){
                ps.executeUpdate();
            }

            // 执行完第一条 SQL 之后,要让第二条 SQL 执行失败

            try(PreparedStatement ps = c.prepareStatement(sql2)){
                ps.executeUpdate();
            }

            // 由于我们关闭了自动提交,所以,所修改的还没有真正的落盘
            c.commit();    // 只有加上这句,事务才被真正的提交了
        }
    }
}

在两条sql语句中打上断点,执行第一条不执行第二条,关闭服务器,执行第二条,会出现错误提示

在Workbench中可以看到第一条语句执行成功了,第二条语句执行失败。

3.有事务,被动失败(重启服务器)

// 有事务,被动失败(重启服务器)
public class Demo3 {
    public static void main(String[] args) throws SQLException {
        String sql1 = "insert into records (rid, bid) values (1, 2)";
        String sql2 = "update books set count = count - 1 where bid = 2";

        // 要使用事务,在同一个事务中,操作 sql1 和 sql2,意味着必须在一条 Connection 完成
        try(Connection c = DBUtil.connection()){
            c.setAutoCommit(false);

            try(PreparedStatement ps = c.prepareStatement(sql1)){
                ps.executeUpdate();
            }

              // 执行完第一条 SQL 之后,要让第二条 SQL 执行失败

            try(PreparedStatement ps = c.prepareStatement(sql2)){
                ps.executeUpdate();
            }

            c.commit();
        }
    }
}

在两条sql语句中打上断点,执行第一条不执行第二条,关闭服务器,执行第二条

在Workbench中可以看到两条语句都没有执行成功,这是事务起到了作用

4.有事务,被动失败(程序导致)

// 有事务,主动失败
public class Demo4 {
    public static void main(String[] args) throws SQLException {
        String sql1 = "insert into records (rid, bid) values (1, 2)";
        String sql2 = "update books set count = count - 1 where bid = 2";

        // 要使用事务,在同一个事务中,操作 sql1 和 sql2,意味着必须在一条 Connection 完成
        try(Connection c = DBUtil.connection()){
            c.setAutoCommit(false);

            try(PreparedStatement ps = c.prepareStatement(sql1)){
                ps.executeUpdate();
            }

            // 执行完第一条 SQL 之后,要让第二条 SQL 执行失败
            // 由于我们代码中的异常导致的被动失败
            int a = 1 / 0;   // 一定会出现 除0 异常,下面的代码就不会执行
                             // 我们没有 catch 除 0 异常,导致异常交给 JVM 去处理
                             // 第一条 SQL 成功、第二条 SQL 失败

            try(PreparedStatement ps = c.prepareStatement(sql2)){
                ps.executeUpdate();
            }

            c.commit();
        }
    }
}

执行这段代码,由于中间有异常,所以第二段代码不会被执行,Workbench中的数据不会有变化。

5.有事务,主动失败(rollback)

// 有事务,主动失败
public class Demo5 {
    public static void main(String[] args) throws SQLException {
        String sql1 = "insert into records (rid, bid) values (1, 2)";
        String sql2 = "update books set count = count - 1 where bid = 2";

        // 要使用事务,在同一个事务中,操作 sql1 和 sql2,意味着必须在一条 Connection 完成
        try(Connection c = DBUtil.connection()){
            c.setAutoCommit(false);

            try(PreparedStatement ps = c.prepareStatement(sql1)){
                ps.executeUpdate();
            }

            try(PreparedStatement ps = c.prepareStatement(sql2)){
                ps.executeUpdate();
            }

            c.rollback();   //主动失败
        }
    }
}

执行程序,虽然两段 sql 都执行了,但最后没有使用commit 使用了 rollback主动失败,所以Workbench中的数据不会被改变。

6.通过事务保护了原子性Atomic

三个场景:

1.开启事务,一切正常情况下,数据会被修改成功(所有 sql 都成功执行了)

2.模拟没有事务时,需要数据库服务器故障的情况

        1.先执行 sql1 

        2.重启服务器

        3.观察数据(一致性被破坏了)

3.模拟有事务时,需要数据库服务器故障的情况

        1.先执行 sql1

        2.重启 mysql 服务器

        3.观察数据(一致性没有被破坏,部分修改的数据被回滚了)

事务只有两个结局:成功(All) 或者 失败(None)。无论成功还是失败,数据的一致性是不能被破坏的。

失败:

1.被动失败:硬件的原因 和 软件的原因(DBMS 本身的原因 和 我们的原因(比如异常))

由于没有执行 commit 所以,事务期间做的“部分修改” 都会被 回滚(rollback)

2.主动失败:我们可以主动要求事务失败(数据回滚)——不去执行 commit ,而去执行 rollback

7.事务的lsolation(隔离性):

多个用户,“同时”操作,同一个资源时,表现为互相之间是互不干扰的。

这句话是 隔离性 追求的理想状态。但实际上,如果精度够高,这个现象是永远达不到的。(时间精度、空间精度)

所以,如果要追求真正的隔离性,实际上是以牺牲并发性(真正的同时)为代价的。

所以 SQL 标准制定了隔离级别(isolation level)

 隔离级别(从左往右:从追求隔离性到追求并发性)

serializeable(可串行性)、snapshot read(快照读)、repeatable read(可重复读)、read committed(读已提交)、read uncommitted(读未提交)

7.1 read uncommitted(读未提交)

多个在执行的事务,可以读取到(看到)其他事务,还处于事务中未提交时的数据修改(完全没隔离)

 这种情况,相当于,t2 读到了“脏数据(dirty data)”,这种副作用被称为—脏读(dirty read),也有不可重复读

7.2 read committed(读已提交)

可以看到别的事务已经提交修改的数据

在同一个事务中(t2),可能出现多次读取同一份数据,得到的结果却是不同的,这个副作用被称为—不可重复读。这里没有脏读了

 7.3 repeatable read(可重复读)

保证了再一次事务中(只要没有提交或者回滚),看到的值是不会变化的(即使有别的事务对这个数据做过修改,并且已经提交了)

只针对表中已有的数据做保护,对于新添加的数据没有保护

7.4 snapshot read(快照读)

快照读,并不是标准中存在的隔离级别

快照读,做到了连幻读这个副作用都没有了

MySQL 中的“可重复读”可以看作实际上是“快照读” ——MVCC

MySQL 的默认情况下,隔离级别就是可重复读

7.5 serializeable(可串行性)

 每个事务都必须排队执行,一次只执行一条事务,在宏观视角上,仍然可以看作“同时”,并发性(性能)差。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值