2022-08-23 学习笔记 day46-sql语言-TCL

本文详细介绍了事务的四大特性(原子性、一致性、隔离性、持久性),并探讨了不同隔离级别下的数据不一致性问题。重点讲解了TCL(事务控制语言)的使用,包括开启事务、设置回滚点、提交和回滚事务。此外,还阐述了封锁协议的概念,如一级、二级和三级封锁协议,以及两段锁协议在确保并发事务可串行化执行中的作用。最后,通过MySQL和Java操作事务的实例展示了事务的实际应用。
摘要由CSDN通过智能技术生成

TCL—事务控制语言

1. 事务

1.1 什么是事务?
  • 事务时最小的业务逻辑单元,不可再分
  • 事务是恢复的基本单位,也是并发控制的基本单位
  • 事务由多条DML语句联合组成,并且要保证事务中所有的DML语句同时成功或同时失败
  • 事务的存在是为了保证数据完整性、安全性
1.2 事务的四大特性
  1. 原子性:事务时最小的工作(执行)单元,不可再分(一个事务要么全做,要么不做)
  2. 一致性:必须保证事务中的多条DML语句同时成功或同时失败(使数据库从一个一致性状态变到另一个一致性状态)
  3. 隔离性:事务之间是有隔离性的,是有隔离级别的,详见下个标题
    Mysql默认隔离级别是——REPEATABLE-READ(可重复读)
    Oracle默认隔离级别是——READ-COMMITTED(读以提交)
  4. 持久性:事务直到数据持久化到硬盘空间才算结束
1.3 事务的四大隔离级别
  1. Read Uncommitted ----> 读未提交
  2. Read Committed ----> 读已提交
  3. Repeatable Read ----> 可重复读
  4. Serializable ----> 序列化读
设置和查看事务的隔离级别

设置事务的会话隔离级别

SET SESSION TRANSACTION ISOLATION LEVEL read uncommitted;  --读未提交
SET SESSION TRANSACTION ISOLATION LEVEL read committed;  --读已提交
SET SESSION TRANSACTION ISOLATION LEVEL repeatable read;  --可重复读
SET SESSION TRANSACTION ISOLATION LEVEL serializable;  --序列化读

设置事务的全局隔离级别

SET GLOBAL TRANSACTION ISOLATION LEVEL read uncommitted;  --读未提交
SET GLOBAL TRANSACTION ISOLATION LEVEL read committed;  --读已提交
SET GLOBAL TRANSACTION ISOLATION LEVEL repeatable read;  --可重复读
SET GLOBAL TRANSACTION ISOLATION LEVEL serializable;  --序列化读

查看事务隔离级别

SELECT @@TRANSACTION_ISOLATION;  -- 查看事务的隔离级别
并发操作带来的数据不一致性
隔离级别丢失修改脏读不可重复度幻读
Read Uncommitted×
Read Committed××
Repeatable Read×××
Serializable××××
  • 丢失修改:事务T1和T2读入同一数据并修改,T2的提交破坏了T1提交的结果,导致T1的修改丢失。这种情况在事务中不可能出现, 因为一个事务在使用UPDATE修改时会给被修改的记录加锁(排他锁), 其他事务必须等待此事务完成后才可以继续UPDATE
  • 脏读:事务T1读到事务T2尚未提交的修改,这里无法确定T2是提交还是回滚,所以称读到的数据为脏数据。
  • 不可重复读:事务T1多次读取同一个数据集合时,事务T1还没结束,事务T2对数据进行修改并提交,导致事务T1两次读取的数据不一样
  • 幻读:事务T1第一次读到的数据是最近提交的数据(可以理解为“快照”),并且事务T1之后所有读操作都是以第一次读到的数据为准(读到的一直都是“快照”中的内容),即便是事务T2对数据进行修改,T1读到的数据同样是第一次读到的数据。
并发控制-封锁
  1. 共享锁/读锁/S锁
    若事务T对数据对象A加S锁,则事务T可以读取A,但不能修改A
    事务T释放A上的S锁之前,其他事务可以给A加S锁,但不能加X锁(即其他事务可以读取A,但不能修改A)
  2. 排他锁/写锁/X锁
    若事务T对数据对象A加X锁,则事务T可以读取A,也可以修改A
    事务T释放A上的X锁之前,其他事务不可以给A加任何类型的锁(即其他事务既不能读取A,也不能修改A)
封锁协议
  • 一级封锁协议:事务T在对数据A修改之前必须先对其加X锁,直到事务结束再释放
  • 二级封锁协议:事务T在对数据A修改之前必须先对其加X锁(一级封锁协议),并且其他事务在读取数据之前必须先对其加S锁,读完再释放
  • 三级封锁协议:事务T在对数据A修改之前必须先对其加X锁(一级封锁协议),并且其他事务在读取数据之前必须先对其加S锁,提交再释放
X锁S锁一致性保证
操作结束释放事务结束释放操作结束释放事务结束释放不丢失修改不读脏数据可重复读
一级封锁协议
二级封锁协议
三级封锁协议
两段锁协议

两段锁协议 (TwoPhase Loking 简称2PL)
指所有的事务必须分为两个阶段对数据项加锁和解锁。

  1. 第一阶段 (生长/扩展/加锁阶段):同一事务对所有数据读写时,首先是要申请并获得数据的封锁。
    在获取封锁阶段,事务可以给数据加锁,在进行数据读操作之前需要申请获取S锁;在进行写操作之前需要申请获取X锁。如果无法获取封锁(加锁失败),则事务进入等待状态,直到加锁成功为止。
  2. 第二阶段 (衰退/收缩/解锁阶段):在事务释放一个封锁后,不能在获取其他的封锁。
    在释放封锁阶段,即在事务释放第一个封锁后进入该阶段,该阶段只能进行解锁操作,不能在进行加锁操作

若并发执行的所有事务都遵循两段锁协议,那么这些事务的任何并发调度策略都是可串行化的,即事务遵循两段锁协议是可串行化的充分不必要条件

2. TCL命令

2.1 开启事务
START TRANSACTION;  --开启事务
2.2 设置回滚点
SAVE POINT A;  --设置回滚点A
2.2 回到回滚点
ROLLBACK TO A;  --回到回滚点A
2.2 结束事务
COMMIT;	 --提交事务
ROLLBACK;  --回滚事务

注意:

  1. 在Mysql中若未开启事务,则默认每一条DML语句作为事务自动提交
  2. 开启事务后,不要掺杂DML以外的语句,否则可能会导致事务无法回滚成功
事务具体使用案例
在Mysql中操作事务
mysql> -- 表的原始数据
mysql> SELECT * FROM emp;
+-------+--------+-----------+------+------------+---------+---------+--------+
| EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO |
+-------+--------+-----------+------+------------+---------+---------+--------+
|  1111 | ghs    | wu        | NULL | NULL       | 3000.00 |    NULL |   NULL |
|  1121 | aaaa   | NULL      | NULL | NULL       |    NULL |    NULL |   NULL |
|  2222 | wy     | NULL      | NULL | NULL       |    1.00 |    NULL |   NULL |
|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |    NULL |     20 |
|  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 |
|  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30 |
|  7566 | JONES  | MANAGER   | 7839 | 1981-04-02 | 2975.00 |    NULL |     20 |
|  7654 | MARTIN | SALESMAN  | 7698 | 1981-09-28 | 1250.00 | 1400.00 |     30 |
|  7698 | BLAKE  | MANAGER   | 7839 | 1981-05-01 | 2850.00 |    NULL |     30 |
|  7782 | CLARK  | MANAGER   | 7839 | 1981-06-09 | 2450.00 |    NULL |     10 |
|  7788 | SCOTT  | ANALYST   | 7566 | 1987-04-19 | 3000.00 |    NULL |     20 |
|  7839 | KING   | PRESIDENT | NULL | 1981-11-17 | 5000.00 |    NULL |     10 |
|  7844 | TURNER | SALESMAN  | 7698 | 1981-09-08 | 1500.00 |    0.00 |     30 |
|  7876 | ADAMS  | CLERK     | 7788 | 1987-05-23 | 1100.00 |    NULL |     20 |
|  7900 | JAMES  | CLERK     | 7698 | 1981-12-03 |  950.00 |    NULL |     30 |
|  7902 | FORD   | ANALYST   | 7566 | 1981-12-03 | 3000.00 |    NULL |     20 |
|  7934 | MILLER | CLERK     | 7782 | 1982-01-23 | 1300.00 |    NULL |     10 |
+-------+--------+-----------+------+------------+---------+---------+--------+
17 rows in set (0.00 sec)

mysql>
mysql> -- 开启事务
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql>
mysql> -- 向表中插入empno=1的数据
mysql> INSERT INTO emp VALUES(1,'1','1',1,'1970-01-01',1.0,1.0,10);
Query OK, 1 row affected (0.00 sec)

mysql>
mysql> -- 向表中插入empno=2的数据
mysql> INSERT INTO emp VALUES(2,'A','2',2,'1970-01-01',2.0,2.0,10);
Query OK, 1 row affected (0.00 sec)

mysql> -- 设置回滚点A
mysql> SAVEPOINT A;
Query OK, 0 rows affected (0.00 sec)

mysql>
mysql>
mysql> -- 向表中插入empno=3的数据
mysql> INSERT INTO emp VALUES(3,'B','3',3,'1970-01-01',3.0,3.0,10);
Query OK, 1 row affected (0.00 sec)

mysql> -- 设置回滚点B
mysql> SAVEPOINT B;
Query OK, 0 rows affected (0.00 sec)

mysql>
mysql> -- 向表中插入empno=4的数据
mysql> INSERT INTO emp VALUES(4,'4','4',4,'1970-01-01',4.0,4.0,10);
Query OK, 1 row affected (0.00 sec)

mysql>
mysql>
mysql> -- 重新查看表数据(可以看到empno为1、2、3、4的数据都被插入到表中了)
mysql> SELECT * FROM emp;
+-------+--------+-----------+------+------------+---------+---------+--------+
| EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO |
+-------+--------+-----------+------+------------+---------+---------+--------+
|     1 | 1      | 1         |    1 | 1970-01-01 |    1.00 |    1.00 |     10 |
|     2 | A      | 2         |    2 | 1970-01-01 |    2.00 |    2.00 |     10 |
|     3 | B      | 3         |    3 | 1970-01-01 |    3.00 |    3.00 |     10 |
|     4 | 4      | 4         |    4 | 1970-01-01 |    4.00 |    4.00 |     10 |
|  1111 | ghs    | wu        | NULL | NULL       | 3000.00 |    NULL |   NULL |
|  1121 | aaaa   | NULL      | NULL | NULL       |    NULL |    NULL |   NULL |
|  2222 | wy     | NULL      | NULL | NULL       |    1.00 |    NULL |   NULL |
|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |    NULL |     20 |
|  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 |
|  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30 |
|  7566 | JONES  | MANAGER   | 7839 | 1981-04-02 | 2975.00 |    NULL |     20 |
|  7654 | MARTIN | SALESMAN  | 7698 | 1981-09-28 | 1250.00 | 1400.00 |     30 |
|  7698 | BLAKE  | MANAGER   | 7839 | 1981-05-01 | 2850.00 |    NULL |     30 |
|  7782 | CLARK  | MANAGER   | 7839 | 1981-06-09 | 2450.00 |    NULL |     10 |
|  7788 | SCOTT  | ANALYST   | 7566 | 1987-04-19 | 3000.00 |    NULL |     20 |
|  7839 | KING   | PRESIDENT | NULL | 1981-11-17 | 5000.00 |    NULL |     10 |
|  7844 | TURNER | SALESMAN  | 7698 | 1981-09-08 | 1500.00 |    0.00 |     30 |
|  7876 | ADAMS  | CLERK     | 7788 | 1987-05-23 | 1100.00 |    NULL |     20 |
|  7900 | JAMES  | CLERK     | 7698 | 1981-12-03 |  950.00 |    NULL |     30 |
|  7902 | FORD   | ANALYST   | 7566 | 1981-12-03 | 3000.00 |    NULL |     20 |
|  7934 | MILLER | CLERK     | 7782 | 1982-01-23 | 1300.00 |    NULL |     10 |
+-------+--------+-----------+------+------------+---------+---------+--------+
21 rows in set (0.00 sec)

mysql>
mysql> -- 回到B回滚点
mysql> ROLLBACK TO B;
Query OK, 0 rows affected (0.00 sec)

mysql> -- 重新查看表数据(可以看出成功回滚到回滚点B)
mysql> SELECT * FROM emp;
+-------+--------+-----------+------+------------+---------+---------+--------+
| EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO |
+-------+--------+-----------+------+------------+---------+---------+--------+
|     1 | 1      | 1         |    1 | 1970-01-01 |    1.00 |    1.00 |     10 |
|     2 | A      | 2         |    2 | 1970-01-01 |    2.00 |    2.00 |     10 |
|     3 | B      | 3         |    3 | 1970-01-01 |    3.00 |    3.00 |     10 |
|  1111 | ghs    | wu        | NULL | NULL       | 3000.00 |    NULL |   NULL |
|  1121 | aaaa   | NULL      | NULL | NULL       |    NULL |    NULL |   NULL |
|  2222 | wy     | NULL      | NULL | NULL       |    1.00 |    NULL |   NULL |
|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |    NULL |     20 |
|  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 |
|  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30 |
|  7566 | JONES  | MANAGER   | 7839 | 1981-04-02 | 2975.00 |    NULL |     20 |
|  7654 | MARTIN | SALESMAN  | 7698 | 1981-09-28 | 1250.00 | 1400.00 |     30 |
|  7698 | BLAKE  | MANAGER   | 7839 | 1981-05-01 | 2850.00 |    NULL |     30 |
|  7782 | CLARK  | MANAGER   | 7839 | 1981-06-09 | 2450.00 |    NULL |     10 |
|  7788 | SCOTT  | ANALYST   | 7566 | 1987-04-19 | 3000.00 |    NULL |     20 |
|  7839 | KING   | PRESIDENT | NULL | 1981-11-17 | 5000.00 |    NULL |     10 |
|  7844 | TURNER | SALESMAN  | 7698 | 1981-09-08 | 1500.00 |    0.00 |     30 |
|  7876 | ADAMS  | CLERK     | 7788 | 1987-05-23 | 1100.00 |    NULL |     20 |
|  7900 | JAMES  | CLERK     | 7698 | 1981-12-03 |  950.00 |    NULL |     30 |
|  7902 | FORD   | ANALYST   | 7566 | 1981-12-03 | 3000.00 |    NULL |     20 |
|  7934 | MILLER | CLERK     | 7782 | 1982-01-23 | 1300.00 |    NULL |     10 |
+-------+--------+-----------+------+------------+---------+---------+--------+
20 rows in set (0.00 sec)

mysql>
mysql> -- 回到A回滚点
mysql> ROLLBACK TO A;
Query OK, 0 rows affected (0.00 sec)

mysql> -- 重新查看表数据(可以看出成功回滚到回滚点A)
mysql> SELECT * FROM emp;
+-------+--------+-----------+------+------------+---------+---------+--------+
| EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO |
+-------+--------+-----------+------+------------+---------+---------+--------+
|     1 | 1      | 1         |    1 | 1970-01-01 |    1.00 |    1.00 |     10 |
|     2 | A      | 2         |    2 | 1970-01-01 |    2.00 |    2.00 |     10 |
|  1111 | ghs    | wu        | NULL | NULL       | 3000.00 |    NULL |   NULL |
|  1121 | aaaa   | NULL      | NULL | NULL       |    NULL |    NULL |   NULL |
|  2222 | wy     | NULL      | NULL | NULL       |    1.00 |    NULL |   NULL |
|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |    NULL |     20 |
|  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 |
|  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30 |
|  7566 | JONES  | MANAGER   | 7839 | 1981-04-02 | 2975.00 |    NULL |     20 |
|  7654 | MARTIN | SALESMAN  | 7698 | 1981-09-28 | 1250.00 | 1400.00 |     30 |
|  7698 | BLAKE  | MANAGER   | 7839 | 1981-05-01 | 2850.00 |    NULL |     30 |
|  7782 | CLARK  | MANAGER   | 7839 | 1981-06-09 | 2450.00 |    NULL |     10 |
|  7788 | SCOTT  | ANALYST   | 7566 | 1987-04-19 | 3000.00 |    NULL |     20 |
|  7839 | KING   | PRESIDENT | NULL | 1981-11-17 | 5000.00 |    NULL |     10 |
|  7844 | TURNER | SALESMAN  | 7698 | 1981-09-08 | 1500.00 |    0.00 |     30 |
|  7876 | ADAMS  | CLERK     | 7788 | 1987-05-23 | 1100.00 |    NULL |     20 |
|  7900 | JAMES  | CLERK     | 7698 | 1981-12-03 |  950.00 |    NULL |     30 |
|  7902 | FORD   | ANALYST   | 7566 | 1981-12-03 | 3000.00 |    NULL |     20 |
|  7934 | MILLER | CLERK     | 7782 | 1982-01-23 | 1300.00 |    NULL |     10 |
+-------+--------+-----------+------+------------+---------+---------+--------+
19 rows in set (0.00 sec)

mysql>
mysql> -- (回滚事务)结束事务
mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)

mysql> -- 重新查看表数据(和原表数据相同)
mysql> SELECT * FROM emp;
+-------+--------+-----------+------+------------+---------+---------+--------+
| EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO |
+-------+--------+-----------+------+------------+---------+---------+--------+
|  1111 | ghs    | wu        | NULL | NULL       | 3000.00 |    NULL |   NULL |
|  1121 | aaaa   | NULL      | NULL | NULL       |    NULL |    NULL |   NULL |
|  2222 | wy     | NULL      | NULL | NULL       |    1.00 |    NULL |   NULL |
|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |    NULL |     20 |
|  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 |
|  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30 |
|  7566 | JONES  | MANAGER   | 7839 | 1981-04-02 | 2975.00 |    NULL |     20 |
|  7654 | MARTIN | SALESMAN  | 7698 | 1981-09-28 | 1250.00 | 1400.00 |     30 |
|  7698 | BLAKE  | MANAGER   | 7839 | 1981-05-01 | 2850.00 |    NULL |     30 |
|  7782 | CLARK  | MANAGER   | 7839 | 1981-06-09 | 2450.00 |    NULL |     10 |
|  7788 | SCOTT  | ANALYST   | 7566 | 1987-04-19 | 3000.00 |    NULL |     20 |
|  7839 | KING   | PRESIDENT | NULL | 1981-11-17 | 5000.00 |    NULL |     10 |
|  7844 | TURNER | SALESMAN  | 7698 | 1981-09-08 | 1500.00 |    0.00 |     30 |
|  7876 | ADAMS  | CLERK     | 7788 | 1987-05-23 | 1100.00 |    NULL |     20 |
|  7900 | JAMES  | CLERK     | 7698 | 1981-12-03 |  950.00 |    NULL |     30 |
|  7902 | FORD   | ANALYST   | 7566 | 1981-12-03 | 3000.00 |    NULL |     20 |
|  7934 | MILLER | CLERK     | 7782 | 1982-01-23 | 1300.00 |    NULL |     10 |
+-------+--------+-----------+------+------------+---------+---------+--------+
17 rows in set (0.00 sec)
在Java中操作事务
package com.jsoft.transaction;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;

public class Demo {

    private static final DataSource DATA_SOURCE;

    static {
        Properties pros = new Properties();
        try {
            pros.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("druid.properties"));
            DATA_SOURCE = DruidDataSourceFactory.createDataSource(pros);
        } catch (IOException e) {
            throw new RuntimeException();
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }
    @Test
    public void test() {

        try(Connection conn = DATA_SOURCE.getConnection();
            PreparedStatement ps = conn.prepareStatement("");) {

            // 查看事务未开启前表的内容
            try (ResultSet rt = ps.executeQuery("SELECT * FROM emp")) {
                StringBuilder sb = new StringBuilder();
                while (rt.next()) {
                    sb.delete(0, sb.length());
                    sb.append(rt.getInt("empno")).append("\t");
                    sb.append(rt.getString("ename")).append("\t");
                    System.out.println(sb);
                }
            }
            System.out.println("----------------------------------------");

            // 关闭事务自动提交
            conn.setAutoCommit(false); // ***注意***:不能等同于start transaction;或begin;
            // 向表中插入empno=1,empno=2的数据
            ps.addBatch("INSERT INTO emp VALUES(1,'1','1',1,'1970-01-01',1.0,1.0,10);");
            ps.addBatch("INSERT INTO emp VALUES(2,'A','2',2,'1970-01-01',2.0,2.0,10);");
            ps.executeBatch();
            // 设置回滚点A
            Savepoint a = conn.setSavepoint("A"); // 等同于 ---> savepoint A;
            // 向表中插入empno=3,empno=4的数据
            ps.addBatch("INSERT INTO emp VALUES(3,'B','3',3,'1970-01-01',3.0,3.0,10);");
            ps.addBatch("INSERT INTO emp VALUES(4,'4','4',4,'1970-01-01',4.0,4.0,10);");
            ps.executeBatch();
            // 回到回滚点A
            conn.rollback(a);
            // 提交事务
            conn.commit();  // 等同于 ---> commit;

            // 查看事务结束后表的内容(如果成功将在表中可以看到只会新增emp=1和emp=2这两条记录)
           try (ResultSet rt = ps.executeQuery("SELECT * FROM emp")) {
                StringBuilder sb = new StringBuilder();
                while (rt.next()) {
                    sb.delete(0, sb.length());
                    sb.append(rt.getInt("empno")).append("\t");
                    sb.append(rt.getString("ename")).append("\t");
                    System.out.println(sb);
                }
            }

            // 开启事务自动提交
            conn.setAutoCommit(true);


        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值