Sql - 事务及mvcc - 1

事务

  • 用来保证数据库的完整性,一组sql要么都执行成功、要么全部不执行

  • 事务的四大特征

    • 原子性:一个事务中的操作要么全部完成、要么全部不完成,假如在中间发生错误,则回滚,此时就像这个事务从来没有发生过一样
    • 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行,导致数据的不一致,隔离性分为4种隔离级别,分别为 读未提交读已提交可重复读(默认)、串行化
    • 持久性:事务执行完成之后,永久保存到磁盘中,即使故障也不会丢失
    • 一致性:上面的三个特质都是为了保证一致性,也就是说在事务开始之前和事务开始之后,数据库的完整性没有被破坏(eg a向b转账,b不可能没有收到钱)
  • 查看隔离级别及设置隔离级别 (只有innodb支持事务)

      // 查看
      mysql> SELECT @@TRANSACTION_ISOLATION;
      +-------------------------+
      | @@TRANSACTION_ISOLATION |
      +-------------------------+
      | REPEATABLE-READ         |
      +-------------------------+
      1 row in set (0.00 sec)
    
      /* 设置隔离级别 */
      //设置read uncommitted级别
      set session transaction isolation level read uncommitted;
    
      //设置read committed级别
      set session transaction isolation level read committed;
    
      //设置repeatable read级别
      set session transaction isolation level repeatable read;
    
      //设置serializable级别
      set session transaction isolation level serializable;
  • 当事务的隔离级别不同时,高并发时会出现脏读、不可重复读、幻读的现象 ,观察以下的name值

    • read uncommitted 级别下脏读现象

        // session1
        mysql> begin;
        Query OK, 0 rows affected (0.00 sec)
      
        // session2
        mysql> begin;
        Query OK, 0 rows affected (0.00 sec)
      
        // session1
        mysql> select * from users;
        +------+--------+------+------------+
        | id   | name   | age  | created_at |
        +------+--------+------+------------+
        |    1 | nn     | NULL | NULL       |
        |    2 | bianca | NULL | NULL       |
        +------+--------+------+------------+
        2 rows in set (0.00 sec)
        // session1
        mysql> update users set name='alex' where id=1;
        Query OK, 1 row affected (0.00 sec)
        Rows matched: 1  Changed: 1  Warnings: 0
        // session2
        mysql> select * from users;
        +------+--------+------+------------+
        | id   | name   | age  | created_at |
        +------+--------+------+------------+
        |    1 | alex   | NULL | NULL       |
        |    2 | bianca | NULL | NULL       |
        +------+--------+------+------------+
        2 rows in set (0.00 sec)

      此时我们可以看到当session1更新了数据但是还没有提交的时候,session2读取到了session1还未提交的数据,这个就叫做脏读,read committed级别将会解决这种问题,使其只能读到提交后的数据。

    • read committed 级别下的不可重复读

        // session1
        mysql> begin;
        Query OK, 0 rows affected (0.00 sec)
      
        mysql> select * from users;
        +------+--------+------+------------+
        | id   | name   | age  | created_at |
        +------+--------+------+------------+
        |    1 | alex   | NULL | NULL       |
        |    2 | bianca | NULL | NULL       |
        +------+--------+------+------------+
        2 rows in set (0.00 sec)
      
        // session2
        mysql> begin;
        Query OK, 0 rows affected (0.00 sec)
      
        mysql> select * from users;
        +------+--------+------+------------+
        | id   | name   | age  | created_at |
        +------+--------+------+------------+
        |    1 | alex   | NULL | NULL       |
        |    2 | bianca | NULL | NULL       |
        +------+--------+------+------------+
        2 rows in set (0.00 sec)
        // session1
        mysql> update users set name='bob' where id=1;
        Query OK, 1 row affected (0.00 sec)
        Rows matched: 1  Changed: 1  Warnings: 0
      
        // session2
        mysql> select * from users;
        +------+--------+------+------------+
        | id   | name   | age  | created_at |
        +------+--------+------+------------+
        |    1 | alex   | NULL | NULL       |
        |    2 | bianca | NULL | NULL       |
        +------+--------+------+------------+
        2 rows in set (0.00 sec)

      此时我们可以看到在session1未提交之前session2读取的还是以前的数据,因此解决了脏读

        // session1
        mysql> commit;
        Query OK, 0 rows affected (0.16 sec)
      
        // session2
        mysql> select * from users;
        +------+--------+------+------------+
        | id   | name   | age  | created_at |
        +------+--------+------+------------+
        |    1 | bob    | NULL | NULL       |
        |    2 | bianca | NULL | NULL       |
        +------+--------+------+------------+
        2 rows in set (0.01 sec)

      此时我们发现当session1提交了之后,session2读取到了session1提交之后的数据,在session2的事务里面我们读取了两次users表的数据,但是两次不一致,这个就叫做不可重复读

    • read committed 级别下的幻读

        // session1
        mysql> begin;
        Query OK, 0 rows affected (0.00 sec)
      
        // session2
        mysql> begin;
        Query OK, 0 rows affected (0.00 sec)
      
        // session1
        mysql> select * from users;
        +------+--------+------+------------+
        | id   | name   | age  | created_at |
        +------+--------+------+------------+
        |    1 | bob    | NULL | NULL       |
        |    2 | bianca | NULL | NULL       |
        |    3 | tay    | NULL | NULL       |
        +------+--------+------+------------+
      
        // session2
        mysql> insert into users(id,name) values(4,'lay');
        Query OK, 1 row affected (0.00 sec)
      
        // session2
        mysql> commit;
        Query OK, 0 rows affected (0.40 sec)
        // session1
        mysql> select * from users;
        +------+--------+------+------------+
        | id   | name   | age  | created_at |
        +------+--------+------+------------+
        |    1 | bob    | NULL | NULL       |
        |    2 | bianca | NULL | NULL       |
        |    3 | tay    | NULL | NULL       |
        |    4 | lay    | NULL | NULL       |
        +------+--------+------+------------+
        4 rows in set (0.00 sec)
      • 此时我们发现第一次查询和第二次查询的数据总量不一致,这种情况下就叫做幻读
    • 当隔离级别设置为可重复读时就会解决上述问题

  • 我们都知道线程在访问同一共享资源的时候,一般通过加锁防止数据出现不一致的情况,但是在高并发下,加锁和解锁是一个很耗时的操作,还容易发生死锁的现象,但是不加锁就会出现上述脏读、不可重复读、幻读的问题,mysql为了解决上述问题,在解决的同时保证高并发的性能,引出了mvcc(多版本并发控制)

    • mvcc真的能够完全解决幻读吗?
    • mvcc的实现方式将在下一节介绍说明
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!