天天看点

数据库中的ACID及隔离级别详解

事务具有以下四种特征:

  • 原子性(Atomicity)

    事务包含的所有操作要么全部成功,要么全部失败回滚。

  • 一致性(Consistency)

    事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

    拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

  • 隔离性(Isolation)

    不同事务并发执行操作相同数据时,每个事务都有各自完整的数据空间,即一个事务内所操作使用的数据与其他并发执行的各事务不互相干扰。

  • 持久性(Durability)

    一个事务一旦提交,对数据库中数据状态变更应该是永久保存下来,即使发生系统崩溃或宕机,也一定能恢复到事务成功结束时的状态。

下面详细讲讲隔离性,在标准SQL规范中,定义了4个事务隔离级别:

  • Read Uncommitted

    读未提交又被称为未授权读取,存在脏读。脏读指一个事务处理过程里读取了另一个未提交的事务中的数据。

  • Read Committed

    读已提交又被称为授权读取,只允许读取已被提交的数据,存在不可重复读。不可重复读指在一个事务内多次查询同一数据,由于存在查询间隔,数据被别的事务修改,返回不同数值。

  • Repeatable Read

    可重复读,保证在同一事务中多次读取同一个数据得到的值都和事务开始时刻一致,存在幻读。幻读是事务非独立执行时发生的一种现象,如事务A对全表数据修改,同时事务B新增了一条数据并提交,事务A的用户发现表中有未修改的数据(事务B新增的那条),如同产生了幻觉。

  • Serializable

    串行化要求事务都被串行执行,即事务只能一个接一个处理,不能并发执行。

数据库中的ACID及隔离级别详解

如图,有三个事务,事务A负责把某个数据项值从1开始,做加1操作,直到变为10后进行事务提交;事务C和A相似,负责将数据项从10逐一加到20并提交;事务B负责读取该数据项的值,面对四种隔离级别,事务B分别能读到的情况:

  • 读未提交:

    能读到未提交事务的数据,因此1到20所有中间值都可以读到。

  • 读已提交:

    只能读取已提交事务的数据,所以读不到中间未被提交的值,如果事务B读取的间隔时间较长,那么会由于不可重复读取读到最开始的1,事务A提交后的10,事务C提交后的20。

  • 可重复读:

    事务B多次读取也只能读到事务开始时刻的值,也就是1。

  • 串行化:

    事务只能一个接一个执行,在事务A和C执行过程中,事务B将不可访问该数据项。

隔离级别对比:

隔离级别 脏读 不可重复读 幻读
读未提交 存在 存在 存在
读已提交 不存在 存在 存在
可重复读 不存在 不存在 存在
串行化 不存在 不存在 不存在

MySQL、Oracle及JDBC中的隔离级别

MySQL

MySQL中,支持上面四种隔离级别,默认的为Repeatable read (可重复读)

  • 查看置隔离级别(全局):

    show global variables like ‘%iso%’;

    select @@tx_isolation;

  • 设置隔离级别(当前会话):

    set tx_isolation=‘read-uncommitted’;

  • 修改全局隔离级别:

    set [glogal | session] transaction isolation level 隔离级别名称;)

ORACLE

Oracle中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed(读已提交)级别。

  • 查看事务隔离级别(当前会话):
    --首先创建一个事务
     declare
          trans_id Varchar2(100);
       begin
          trans_id := dbms_transaction.local_transaction_id( TRUE );
       end; 
     --查看事务隔离级别
     SELECT s.sid, s.serial#,
       CASE BITAND(t.flag, POWER(2, 28))
         WHEN 0 THEN 'READ COMMITTED'
         ELSE 'SERIALIZABLE'
       END AS isolation_level
     FROM v$transaction t
     JOIN v$session s ON t.addr = s.taddr AND s.sid = sys_context('USERENV', 'SID');
               
  • 设置隔离级别(全局):

    SET TRANSACTION ISOLATION LEVEL [READ COMMITTED |SERIALIZABLE]

  • 设置隔离级别(当前会话)

    ALTER SESSION SET ISOLATION_LEVEL [READ COMMITTED |SERIALIZABLE]

JDBC

JDBC中修改事务隔离级别,需要在setAutoCommit(false)方法之前调用Connection的setTransactionIsolation(level),level取值如下。

数据库中的ACID及隔离级别详解

下面在mysql中演示了隔离级别分别为读未提交、读已提交、可重复读存在的问题,为了更直接展示结果,不使用第三方工具直接用mysql 来操作:

数据库中的ACID及隔离级别详解

首先创建一张表,有主键id,值value以及状态status,建表sql:

mysql> CREATE TABLE `mytable` (
    -> `id`  int(11) NOT NULL COMMENT 'id' ,
    -> `value`  int(11) NULL COMMENT '值' ,
    -> `status`  smallint(3) NULL COMMENT '状态' ,
    -> PRIMARY KEY (`id`)
    -> );
Query OK, 0 rows affected (0.23 sec)
-- 查看mysql默认隔离级别
mysql> show global variables like '%iso%';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.01 sec)
-- 也可以通过以下sql查看隔离级别
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.01 sec)
-- 插入一条数据并查看
mysql> insert into mytable values(1,10,0);
Query OK, 1 row affected (0.06 sec)

mysql> select * from mytable;
+----+-------+--------+
| id | value | status |
+----+-------+--------+
|  1 |    10 |      0 |
+----+-------+--------+
1 row in set (0.01 sec)
           

验证读未提交存在脏读问题: 打开两个mysql窗口,将隔离级别改为读未提交,并关闭自动提交

mysql> set tx_isolation='read-uncommitted';
Query OK, 0 rows affected (0.03 sec)
-- 关闭自动提交
mysql> set autocommit=0;
Query OK, 0 rows affected (0.07 sec)
-- 开启事务
mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)
-- 更新数据,将value值加10
mysql> update mytable set value = 20 where id =1;
Query OK, 1 row affected (0.12 sec)
Rows matched: 1  Changed: 1  Warnings: 0
           

打开第二个窗口,并将隔离级别改为读未提交

mysql> set tx_isolation='read-uncommitted';
Query OK, 0 rows affected (0.07 sec)
-- 可以看到窗口一中未提交的事务
mysql> select * from mytable;
+----+-------+--------+
| id | value | status |
+----+-------+--------+
|  1 |    20 |      0 |
+----+-------+--------+
1 row in set (0.01 sec)
           

验证读未提交不存在脏读但存在不可重复读问题: 将窗口一中的修改提交后,修改窗口一和二的隔离级别为读已提交并开启事务,在窗口一中修改数据为30

数据库中的ACID及隔离级别详解

在窗口二中查询数据,发现未提交的数据并不能读到

数据库中的ACID及隔离级别详解

在窗口一中提交修改后在窗口二中可以读到窗口一中修改的数据,脏读不存在了

数据库中的ACID及隔离级别详解

继续在窗口一中修改数据,将value修改为40,并提交

数据库中的ACID及隔离级别详解

窗口二在同一个事务中可以读到窗口一再次修改的值,出现了不可重复读

数据库中的ACID及隔离级别详解

**验证可重复读不存在不可重复读问题:**将窗口一二的隔离级别改为可重复读做测试,先在窗口二中开启事务,读取数据结果为40

数据库中的ACID及隔离级别详解

窗口一中将数据改为50,并提交

数据库中的ACID及隔离级别详解

再在窗口二中查看数据结果还是40,不可重复读不存在,只有结束本次事务才能读到窗口一中修改的值

数据库中的ACID及隔离级别详解

参考:

https://blog.csdn.net/zh521zh/article/details/69400053

https://www.cnblogs.com/who-else/p/6659564.html