zoukankan      html  css  js  c++  java
  • MySQL进阶系列:事务及事务隔离级别;

    当前环境版本5.7.21

    mysql> select version();
    +-----------+
    | version() |
    +-----------+
    | 5.7.21    |
    +-----------+
    1 row in set (0.00 sec)

    一. 什么是事务

    事务就是现实中抽象出来一种逻辑操作,要么都执行,要么都不执行,不能存在部分执行的情况。

    比较经典的案例就是银行转账:小A向小B转账100元

    正常的情况:小A的账户扣减100元,小B的账户增加100元。

    非正常情况: 小A的账户扣减100元,小B账户金额不变。

    非正常情况下,小A账户扣减100之后银行系统出现问题,小B账户增加100元的操作并没有执行。也就是两边金额对不上了,小A不愿意,小B不愿意,银行也不愿意啊。事务的出现就是为了避免非正常情况的出现,让大家都满意。

    我们常见的MySQL存储引擎中只有InnoDB是支持事务的

    二. 事务的4大特性(ACID)

    1. 原子性(Atomicity)

    事务的操作是不可分割的,要么都操作,要么都不操作,就像转账一样,不存在中间状态。而且这个原子性不是说只有一个动作,可能会有很多的操作,但是从结果上看是不可分割的,也就是说原子性是一个结果状态。

    2. 一致性(Consistency)

    执行事务的前后,数据保持一致,就像银行账户系统一样无论事务是否成功,两者的账户总额应该是一样的。

    3. 隔离性(Isolation)

    多个事务同时操作数据的时候,多个事务直接互相隔离,不会互相影响。

    4. 持久性(Durability)

    一个事务在提交后对数据的影响是永久的,写入磁盘中不会丢失。

    三. 显式事务、隐式事务

    mysql的事务分为显式事务隐式事务,默认的事务是隐式事务,由变量autocommit 在操作的时候会自动开启,提交,回滚。

    控制的关键命令如下

    set autocommit=0; -- 关闭自动提交事务(显式)
    set autocommit=1; -- 开启自动提交事务(隐式)
    -- 当autocommit=0的时候手动控制事务
    rollback; -- 回滚事务
    commit;  -- 提交事务
    -- 当autocommit=1 自动提交事务,但是可以控制手动提交
    start transaction; -- 开启事务(或者用begin开启事务)
    commit; -- 提交事务
    rollback; -- 回滚事务
    SAVEPOINT 保存点名称;  -- 保存点(相当于存档,可以不用回滚全部操作)
    rollback to  保存点;  -- 回滚到某个保存点 (这个后面就不测试,知道有这个操作就行)
    
    

    先建一张表ajisun

    mysql> create table ajisun(id int(5), name varchar(20) character set utf8  COLLATE utf8_bin ) engine=innodb character set= utf8mb4 COLLATE = utf8mb4_bin;
    Query OK, 0 rows affected (0.03 sec)

     

    1. 隐式事务 

    -- 看下当前autocommit的状态是,默认是on状态
    mysql> show variables like 'autocommit';  
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | autocommit    | ON    |
    +---------------+-------+
    1 row in set (0.01 sec)
    ​
    --  插入一条数据
    mysql> insert into ajisun values(1,'阿纪');
    Query OK, 1 row affected (0.00 sec)
    ​
    mysql> rollback;
    -- 执行rollback 也是没有效果的,还是能够查询到插入的数据(不需要我们手动控制commit)
    mysql> select * from ajisun;
    +------+--------+
    | id   | name   |
    +------+--------+
    |    1 | 阿纪   |
    +------+--------+
    1 row in set (0.00 sec)
    2. 显式事务方式1

    显式事务由我们自己控制事务的开启,提交,回滚等操作

    -- 开启显式事务-回滚
    mysql> set autocommit=0;
    Query OK, 0 rows affected (0.00 sec)
    ​
    mysql> select * from ajisun;
    +------+--------+
    | id   | name   |
    +------+--------+
    |    1 | 阿纪   |
    +------+--------+
    1 row in set (0.00 sec)
    ​
    mysql> insert into ajisun values(2,'纪先生');
    Query OK, 1 row affected (0.00 sec)
    -- 插入后可以看见2条数据
    mysql> select * from ajisun;
    +------+-----------+
    | id   | name      |
    +------+-----------+
    |    1 | 阿纪      |
    |    2 | 纪先生    |
    +------+-----------+
    2 rows in set (0.00 sec)
    -- 回滚之后上面插入的数据就没了
    mysql> rollback;
    Query OK, 0 rows affected (0.00 sec)
    ​
    mysql> select * from ajisun;
    +------+--------+
    | id   | name   |
    +------+--------+
    |    1 | 阿纪   |
    +------+--------+
    1 row in set (0.00 sec)
    
    
    -- 插入一条数据
    mysql> insert into ajisun values(2,'ajisun');
    Query OK, 1 row affected (0.01 sec)
    -- 提交
    mysql> commit;
    Query OK, 0 rows affected (0.00 sec)
    -- 回滚
    mysql> rollback;
    Query OK, 0 rows affected (0.00 sec)
    -- 先提交commit,在rollback 数据依然存在,说明commit生效,事务已提交,回滚就不生效了。
    mysql> select * from ajisun;
    +------+--------+
    | id   | name   |
    +------+--------+
    |    1 | 阿纪   |
    |    2 | ajisun |
    +------+--------+
    2 rows in set (0.00 sec)
    
    

    3. 显式事务方式2

    使用start transaction

    先改成默认的事务 set autocommit=1;

    -- 开启事务
    mysql> start transaction;
    Query OK, 0 rows affected (0.00 sec)
    ​
    mysql> delete from ajisun where id=1;
    Query OK, 1 row affected (0.00 sec)
    -- 提交事务
    mysql> commit;
    Query OK, 0 rows affected (0.01 sec)
    ​
    mysql> select * from ajisun;
    +------+--------+
    | id   | name   |
    +------+--------+
    |    2 | ajisun |
    +------+--------+
    1 row in set (0.00 sec)
    -- 开启事务
    mysql> start transaction;
    Query OK, 0 rows affected (0.00 sec)
    ​
    mysql> delete from ajisun where id =2;
    Query OK, 1 row affected (0.01 sec)
    -- 回滚事务
    mysql> rollback;
    Query OK, 0 rows affected (0.01 sec)
    --  删除操作失效了
    mysql> select * from ajisun;
    +------+--------+
    | id   | name   |
    +------+--------+
    |    2 | ajisun |
    +------+--------+
    1 row in set (0.00 sec)

    四. 并发事务中的问题

    如果对表的操作同一时间只有一个事务就不会有问题,但是这是不可能的。现实中都是尽可能的利用,多个事务同时操作。多个事务就会带来不少的问题,例如脏读脏写`不可重复读幻读

    1. 脏读

    一个事务读取到另一个未提交事务修改后的数据 这就是脏读。

    例如两个事务a,b: 同时操作一条记录

    a事务修改记录后还没有正式提交到数据库,这时b事务去读取,然后用读取到的数据进行后续操作。

    如果a事务回滚了,这个修改后的数据就不存在了,那么b事务就是在使用一个不存在的数据。这种就是脏数据。

    2. 脏写(数据丢失)

    一个事务修改了另一个未提交事务修改过的数据

    例如两个事务a,b: 同时操作一条记录

    a事务修改后没有提交, 接着b事务也修改同一条数据,然后b事务提交数据。

    如果a事务回滚自己的修改,同时也把b事务的修改也回滚了,造成的问题就是:b事务修改了 也提交了,但是数据库并没有改变,这种情况就是脏写。

    3. 不可重复读

    一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值。

    也就是在同一个事务中多次读取同一条记录,得到的内容都不一样(在每次读取之前都有其他事务完成修改并提交),这就是不可重复读

    4. 幻读

    在一个事务内 相同条件查询数据,先后查询到的记录数不一样

    也就是一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来,那就意味着发生了幻读

    不可重复读和幻读的区别:不可重复读重点在于同一条记录前后数据值不一样(内容的变化),而幻读重点在于相同查询条件前后所获取的记录数不一样(条数的变化)

     

    五. 事务的隔离级别

    上面说的事务的并发问题,在不同的场景下要求不一样,能接受的问题也不一样。他们之间的严重性排序如下:

    脏写 > 脏读 > 不可重复读 > 幻读

    MySQL中提供了4种隔离级别来处理这几个问题,如下

    隔离级别脏读不可重复读幻影读
    READ- UNCOMMITTED
    READ-COMMITTED ×
    REPEATABLE-READ × ×
    SERIALIZABLE × × ×

    SQL 标准定义了四个隔离级别:

    • READ-UNCOMMITTED(读未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读不可重复读幻读。但是并发度最高

    • READ-COMMITTED(读已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读不可重复读仍有可能发生。

    • REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读不可重复读,但幻读仍有可能发生。

    • SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,该级别可以防止脏读不可重复读以及幻读。并发度也是最低的

    MySQL默认采用的 REPEATABLE_READ 隔离级别 
    Oracle默认采用的 READ_COMMITTED 隔离级别

     

    1. 如何设置隔离级别

    可以通过变量参数transaction_isolation 查看隔离级别

    mysql> SELECT @@transaction_isolation;
    +-------------------------+
    | @@transaction_isolation |
    +-------------------------+
    | REPEATABLE-READ         |
    +-------------------------+
    1 row in set (0.00 sec)
    ​
    mysql> show variables like '%transaction_isolation%';
    +-----------------------+-----------------+
    | Variable_name         | Value           |
    +-----------------------+-----------------+
    | transaction_isolation | REPEATABLE-READ |
    +-----------------------+-----------------+
    1 row in set (0.02 sec)

    修改的命令:SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL $[level];

    level的值就是4中隔离级别READ-UNCOMMITTED READ-COMMITTED REPEATABLE-READ SERIALIZABLE

    1. 设置全局隔离级别

    只对执行完该语句之后产生的会话起作用。

    当前已经存在的会话无效。

    set global transaction_isolation='read-uncommitted';
    set global transaction_isolation='read-committed';
    set global transaction_isolation='repeatable-read';
    set global transaction_isolation='serializable';

    例如:

    会话A

    mysql> set global transaction_isolation='serializable';
    Query OK, 0 rows affected (0.01 sec)
    mysql> select @@global.transaction_isolation;
    +--------------------------------+
    | @@global.transaction_isolation |
    +--------------------------------+
    | SERIALIZABLE                   |
    +--------------------------------+
    1 row in set (0.00 sec)
    -- 当前会话(设置之前就已经存在的会,级别是默认的)
    mysql> select @@transaction_isolation;
    +-------------------------+
    | @@transaction_isolation |
    +-------------------------+
    | REPEATABLE-READ         |
    +-------------------------+
    1 row in set (0.00 sec)

    会话B(set之后新建的会话)

    mysql> select @@global.transaction_isolation;
    +--------------------------------+
    | @@global.transaction_isolation |
    +--------------------------------+
    | SERIALIZABLE                   |
    +--------------------------------+
    1 row in set (0.00 sec)
    ​
    mysql> select @@transaction_isolation;
    +-------------------------+
    | @@transaction_isolation |
    +-------------------------+
    | SERIALIZABLE            |
    +-------------------------+
    1 row in set (0.00 sec)

     

    2. 设置会话的隔离级别

    对当前会话的所有后续的事务有效

    该语句可以在已经开启的事务中间执行,但不会影响当前正在执行的事务。

    如果在事务之间执行,则对后续的事务有效。

    set session transaction_isolation='read-uncommitted';
    set session transaction_isolation='read-committed';
    set session transaction_isolation='repeatable-read';
    set session transaction_isolation='serializable';

    比如:

    会话A

    mysql> set session transaction_isolation='read-uncommitted';
    Query OK, 0 rows affected (0.00 sec)
    ​
    mysql> select @@transaction_isolation;
    +-------------------------+
    | @@transaction_isolation |
    +-------------------------+
    | READ-UNCOMMITTED        |
    +-------------------------+
    1 row in set (0.00 sec)
    新建会话B(依然是默认的级别:可重复读)
    
    mysql> select @@transaction_isolation;
    +-------------------------+
    | @@transaction_isolation |
    +-------------------------+
    | REPEATABLE-READ         |
    +-------------------------+
    1 row in set (0.00 sec)

    2. 怎么选择隔离级别

    一般情况下默认的可重复读就好了,一般很少改这个,除非业务场景特殊

    记住一点:隔离级别越高,并发问题就越少,但并发性也就越低,所以还是要根据业务选择来。

     

    六. 总结

    1. 事务的四大特性:原子性,一致性,隔离性,持久性

    2. 事务的常见命令:

      set autocommit=0/1; -- 关闭/开启自动提交事务
      start transaction; -- 开启事务(或者用begin)
      rollback; -- 回滚事务
      commit; -- 提交事务
    3. 并发事务的问题:脏写 > 脏读 > 不可重复读 > 幻读

    4. 需要熟悉事务的4种隔离级别以及MySQL默认级别

    5. 怎么设置隔离级别(global,session)

     

    关注不迷路

    MySQL高级相关更多内容,如事务,锁,MVCC,读写分离,分库分表等还在持续更新中,欢迎关注催更。

    我是阿纪,用输出倒逼输入而持续学习,持续分享技术系列文章,以及全网值得收藏好文,欢迎关注公众号,做一个持续成长的技术人。

     

    MySQL系列的历史文章

    1. MySQL进阶系列:一文了解mysql基础架构

    2. MySQL进阶系列:一文了解mysql存储引擎

    3. MySQL进阶系列:mysql中MyISAM和InnoDB有什么区别;

    4. MySQL进阶系列:mysql中表设计如何更好的选择数据类型;

    5. MySQL进阶系列:数据库设计中的范式究竟该如何使用

    6. MySQL进阶系列:一文详解explain各字段含义

    7. MySQL进阶系列:为什么mysql使用B+作为索引的数据结构

    8. MySQL进阶系列:  你需要知道的一些索引基础知识;

    9. MySQL进阶系列:怎么创建索引更合适;

    10. MySQL进阶系列:主从复制原理和配置;

    11. MySQL进阶系列:join连接的原理-3种算法;

     

  • 相关阅读:
    说说那些低级错误
    vue双向数据绑定
    妙用$.extend
    写一个限制执行次数的函数
    强类型语言不好的地方,比如这样:
    form表单里的坑
    Java Day 09
    Java Day 08
    Java Day 07
    Java Day 06
  • 原文地址:https://www.cnblogs.com/sunjiguang/p/15733417.html
Copyright © 2011-2022 走看看