MYSQL事务-隔离级别

作者: 波罗学的草稿 | 来源:发表于2016-11-30 01:32 被阅读118次

    GIT还没有更新完成,最近因为工作原因要解决MYSQL的一些问题,转而开始重新学习MYSQL。本想把多版本并发控制MVCC也一并说明,但理解不够透彻,待总结再续。

    本篇文章的重点在于总结MYSQL事务。

    事务是什么?

    事务简言之就是一组SQL执行要么全部成功,要么全部失败。MYSQL的事务在存储引擎层实现。

    事务都有ACID特性:

    • 原子性(Atomicity):一个事务必须被视为一个不可分割的单元;
    • 一致性(Consistency):数据库总是从一种状态切换到另一种状态;
    • 隔离性(Isolation):通常来说,事务在提交前对于其他事务不可见;
    • 持久性(Durablity):一旦事务提交,所做修改永久保存数据库;

    事务最常用的例子就是银行转账。假设polo需给lynn转账1000元,如下步骤:

    • 确认polo账户余额高于1000元;
    • 从polo的账户余额减去1000元;
    • 将lynn的账户余额增加1000元;

    SQL语句如下:

    mysql> BEGIN;
    mysql> SELECT balance FROM bank_account WHERE uid=10001;
    mysql> UPDATE bank_account SET balance=balance-1000 WHERE uid=10001;
    mysql> UPDATE bank_account SET balance=balance+1000 WHERE uid=10002;
    mysql> COMMIT;
    

    注:mysql启动事务可使用BEGIN或者START TRANSACTION;

    上述三个步骤执行在一个事务中就能够保证数据的完整性,要么全部成功,要么全部失败。

    MYSQL提供两种事务型引擎:Innodb和NDBCluster。默认采用自动提交模式,执行一条语句自动COMMIT。通过AUTOCOMMIT变量可启用或者禁用自动提交模式:

    mysql> SHOW VARIABLES LIKE "AUTOCOMMIT";
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | autocommit    | ON    |
    +---------------+-------+
    1 row in set (0.00 sec)
    
    mysql> SET AUTOCOMMIT=1
    

    AUTOCOMMIT=1表示开启默认提交,0表示关闭默认提交需要手动提交。

    事务隔离级别

    事务隔离性的解释:通常情况下,事务在提交之前对于其他事务不可见。

    数据库有四种隔离级别,当然MYSQL也是如此。

    本人理解,隔离级别就是决定一个事务的修改另一个事务什么情况下能看到。
    书本解释,每种级别都规定了一个事务中所做修改,哪些在事务内和事务间是可见的。

    区别在于是否存在事务内可见性的规定。我在四个级别似乎没有看到

    下面开始说明MYSQL的四种隔离级别,先准备一张学生表:

    mysql> CREATE TABLE `student` (
     `id` int(11) NOT NULL AUTO_INCREMENT,
     `name` varchar(32) NOT NULL DEFAULT '',
     PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 |
    

    只有id(主键自增)与name字段

    • READ UNCOMMITTED(未提交读)
      事务中修改没有提交对其他事务也是可见的,俗称脏读。非常不建议使用。

      实例演示
      客户端A和B设置隔离级别为未提交读

      mysql> SET SESSION TX_ISOLATION='READ-UNCOMMITTED';
      

      客户端A与B开启事务并查询student

      mysql> BEGIN;
      mysql> SELECT * FROM student;
      Empty set (0.00 sec)
      

      客户端A和B都是空数据

      客服端B插入一条新的数据

      mysql> INSERT INTO student(name) VALUES("polo");
      Query OK, 1 row affected (0.00 sec)
      

      此时事务未提交,客服端A查看student表

      $ SELECT * FROM student;
      mysql> SELECT * FROM student;
      +----+------+
      | id | name |
      +----+------+
      |  1 | polo |
      +----+------+
      1 row in set (0.00 sec)
      

      <font color=red>客户端A看到B未提交的修改</font>

      客户端B执行回滚操作

      mysql> ROLLBACK
      

      成功之后,客户端A查看student表

      mysql> SELECT * FROM student;
      Empty set (0.00 sec)
      

      <font color=red>客户端A查看数据为空</font>

      以上可以看出未提交读隔离级别的危险性,对于一个没有提交事务所做修改对另一个事务是可见状态,容易造成脏读。非特殊情况不得使用此级别

    • READ COMMITTED(提交读)
      多数数据库系统默认为此级别(MYSQL不是)。已提交读级别即为一个事务只能已提交事务所做的修改,也就解决了未提交读的问题,即脏读的问题。

      实例演示
      客户端A和B设置隔离级别为已提交读

      mysql> SET SESSION TX_ISOLATION='READ-COMMITTED';
      

      客户端A与B开启事务并查询student

      mysql> BEGIN;
      mysql> SELECT * FROM student;
      Empty set (0.00 sec)
      

      客户端A和B都为空
      客服端B插入一条新的数据,不提交

      mysql> INSERT INTO student (name) VALUES('polo');
      

      客户端A查看student

      mysql> SELECT * FROM student;
      Empty set (0.00 sec)
      

      <font color=red>注意这里与上面不同了,在客户端B没有提交事务情况下无数据</font>
      下面客户端B提交事务

      mysql> COMMIT;
      

      客户端A再次查看student表。

      mysql> SELECT * FROM student;
      +----+------+
      | id | name |
      +----+------+
      |  1 | polo |
      +----+------+
      1 row in set (0.00 sec)
      

      成功读取到客户

      从上面的示例可以看出,提交读没有了未提交读的问题,但我们可以看到在客户端A的一个事务中执行两次同样的SELECT语句得到不同结果,因此已提交读又被称为不可重复读。同样筛选条件可能得到不同的结果。

    • REPEATABLE READ(可重复读)
      如其名也,解决已提交读不可重复读取的问题。

      示例演示
      客户端A和B设置隔离级别为可重复读

      mysql> SET SESSION tx_isolation='REPEATABLE-READ'
      

      客户端A与B开启事务并查看

      mysql> BEGIN;
      mysql> SELECT * FROM student;
      +----+------+
      | id | name |
      +----+------+
      |  1 | polo |
      +----+------+
      1 rows in set (0.00 sec)
      

      客服端B更新polo为adam,并提交事务

      mysql> UPDATE student SET name='adam' WHERE id=1;
      mysql> COMMIT
      

      客户端A查看student表

      mysql> SELECT * FROM student;
      +----+------+
      | id | name |
      +----+------+
      |  1 | polo |
      +----+------+
      1 rows in set (0.00 sec)
      

      <font color=red>注意客户端A查看数据未变,没有不可重复读问题</font>

      客户端A提交事务,并查看student表

      mysql> COMMIT;
      mysql> SELECT * FROM student;
      +----+------+
      | id | name |
      +----+------+
      |  1 | polo |
      +----+------+
      1 rows in set (0.00 sec)
      

      上面实例可知,可重复读两次读取内容一样。数据库这级别并没有解决幻读的问题。但是MYSQL在可重复读基础上增加了MVCC机制解决了此问题,实例无法演示幻读效果。

      那什么是幻读?首先,可重复读锁定范围为当前查询到的内容,如执行

      mysql> SELECT * FROM student WHERE id>=1
      

      锁定的即id>=1查到的行,为行级锁。如另一事务执行并默认提交以下语句

      mysql> INSERT INTO student (name) VALUES ('stephen');
      

      新增的这行并没有被锁定,此时读取student

      mysql> SELECT * FROM student WHERE id>=1;
      +----+---------+
      | id | name    |
      +----+---------+
      |  1 | polo    |
      |  2 | stephen |
      +----+---------+
      2 rows in set (0.00 sec)
      

      便出现了幻读

      除了使用MYSQL的MVCC机制,还可以使用可串行化隔离级别解决此问题。

    • SEAIALIZABLE(可串行化)
      可串行化是最高隔离级别,强制事务串行执行。执行串行了也就解决了一切的问题,这个级别只有在对数据一致性要求非常严格且没用并发的情况下使用

      实例演示
      客户端A和B设置隔离级别为可串行化

      mysql> SET SESSION tx_isolation='SERIALIZABLE';
      

      客户端A执行查询

      mysql> SELECT * FROM student WHERE id<4;
      +----+---------+
      | id | name    |
      +----+---------+
      |  1 | polo    |
      |  2 | stephen |
      +----+---------+
      2 rows in set (0.00 sec)
      

      客户端B执行新增

      mysql> INSERT INTO student (name) VALUES('yunteng');
      

      好的!效果出现了,此时我们会发现INSERT语句被阻塞执行,原因就是A执行了查询表student同时满足id<4,已被锁定。如果查询表student条件为id<3,则新增语句可正常执行。

    隔离级别对照图

    隔离级别 脏读 不可重复读 幻读 加锁读
    未提交读
    提交读
    可重复读
    未提交读

    好了,关于事务的隔离级别就说这么多,希望自己的理解没有错误。

    本人资历尚浅,如有错误请指正。谢谢!

    Paste_Image.png

    相关文章

      网友评论

        本文标题:MYSQL事务-隔离级别

        本文链接:https://www.haomeiwen.com/subject/etqbpttx.html