zoukankan      html  css  js  c++  java
  • MySQL 事务

    简介

    事务是一组原子性的 SQL 查询, 或者说是一个独立的工作单元. 在事务内的语句, 要么全部执行成功, 要么全部执行失败.

    事务的 ACID 性质

    数据库事务拥有以下四个特性, 即 ACID 性质:

    • 原子性(Atomicity): 事务作为一个整体被执行, 包含在其中的对数据库的操作要么全部执行成功, 要么全部失败回滚. 对于一个事务来说, 不可能只执行其中一部分操作, 这就是事务的原子性.
    • 一致性(Consistency): 事务应确保数据库的状态从一个一致状态转变为另一个一致状态.
    • 隔离性(Isolation): 多个事务并发执行时, 一个事务的执行不应影响其他事务的执行.
    • 持久性(Durability): 已被提交的事务对数据库的修改应该永久保存在数据库中.

    事务的隔离级别

    SQL 中定义了四种隔离级别, 每种级别都规定了一个事务所做的修改,哪些在事务内和事务间是可见的, 哪些是不可见的. 较低的隔离通常可以执行更高的并发, 系统的开销也更低.
    SQL 标准中的四种隔离有:

    • READ UNCOMMITED(未提交读)
      在 READ UNCOMMITED 级别中, 事务的修改, 即使没有提交, 对其他事务也是可见的. 其他事务可以读取此事务中的未提交的数据, 这也被称为脏读(Dirty Read). 此事务隔离级别会导致很多问题, 并且性能也不会比其他事务隔离级别好多少, 因此在实际环境中很少使用.
    • READ COMMITED(提交读)
      大多数的数据库默认隔离级别都是 READ COMMITED, 但是 MySQL 并不是. 在 READ COMMITED 级别中, 一个事务从开始到提交之前, 所做的任何修改对其他事务都是不可见的.
      这个级别有时候也叫做不可重复读(nonrepeatable read), 因为两次执行相同的查询, 可能会得到不一样的结果.
    • REPEATABLE READ(可重复读)
      REPEATABLE READ 解决了脏读的问题. 该级别保证了在同一个事务中多次读取同样记录的结果时一致的. 但是理论上, 可重复读隔离级别还是无法解决另一个幻读(Phantom Read)的问题. 所谓幻读, 指的是当某个事务在读取某个范围内的记录时, 另外一个事务又在该范围内插入了新的记录, 当之前的事务再次读取该范围的记录时, 会产生幻行(Phantom Row).
      可重复读是 MySQL 的默认事务隔离级别.
    • SERIALIZABLE(可串行化)
      SERIALIZABLE 是最高的隔离级别. 他通过强制事务串行执行, 避免了前面说的幻读的问题. 简单来说, SERIALIZABLE 会在读取的每一行数据上都加上锁, 所以可能导致大量的超时和锁争用的问题. 实际应用中也很少用到这个隔离级别, 只有在非常需要确保数据的一致性而且可以接受没有并发的情况下, 才考虑采用该级别.

    死锁

    死锁是指两个或多个事务在同一个资源上相互占用, 并请求锁定对方占用的资源, 从而导致恶性循环的现象. 当多个事务试图以不同顺序锁定资源时, 就可能产生死锁.
    死锁发生以后, 只有部分或者完全回滚其中一个事务, 才能打破死锁.

    MySQL 中的事务

    在 MySQL 提供的众多存储引擎中, 只有 InnoDB 和 NDB Cluster 支持事务.

    关于自动提交(AUTOCOMMIT)

    MySQL 默认采用自动提交(AUTOCOMMIT) 模式. 即如果不显示地开始一个事务, 则每个操作都被当做一个事务执行提交操作.
    我们可以通过

    SHOW VARIABLES LIKE 'autocommit';
    

    查询当前是否已经开启了字段提交事务, 例如:

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

    如果是 ON, 则表示已经开启了, 0 或 OFF 表示禁用.
    可以通过 "set autocommit=0;" 来禁用自动提交:

    set autocommit=0;
    

    对于非事务型存储引擎, 例如 MyISAM, 修改 AUTOCOMMIT 属性试不会有影响的.

    自动提交和非自动提交的区别

    下面以一个例子来展示 autocommit 启动和非启动时的区别.
    首先建立一个测试用的表:

    CREATE TABLE `user` (
      `id`   BIGINT(20) NOT NULL AUTO_INCREMENT,
      `name` VARCHAR(20)         DEFAULT '',
      `age`  INT(11)             DEFAULT '0',
      PRIMARY KEY (`id`)
    )
      ENGINE = InnoDB
      DEFAULT CHARSET = utf8mb4
    

    接着关闭自动提交功能:

    mysql> set autocommit=0;
    

    然后插入一个数据:

    mysql> INSERT INTO user (`id`, `name`, `age`) VALUES (1, 'xys', 18);
    

    接着查看数据:

    mysql> select * from user;
    +----+------+------+
    | id | name | age  |
    +----+------+------+
    |  1 | xys  |   18 |
    +----+------+------+
    1 row in set (0.00 sec)
    

    数据库中可以查询到这条数据了.
    但是我们通过 SHOW BINLOG EVENTS 查看操作日志:

    mysql> show binlog events;
    +------------------+-----+----------------+-----------+-------------+---------------------------------------+
    | Log_name         | Pos | Event_type     | Server_id | End_log_pos | Info                                  |
    +------------------+-----+----------------+-----------+-------------+---------------------------------------+
    | mysql-bin.000001 |   4 | Format_desc    |         1 |         123 | Server ver: 5.7.13-log, Binlog ver: 4 |
    | mysql-bin.000001 | 123 | Previous_gtids |         1 |         154 |                                       |
    +------------------+-----+----------------+-----------+-------------+---------------------------------------+
    2 rows in set (0.00 sec)
    

    会发现上面的输出中, 并没有插入数据相关的记录, 并且此时如果我们退出 MySQL 的话, 那么我们插入的数据并没有被保存:

    mysql> exit
    Bye
    >>> mysql -u root -p
    Enter password:
    
    mysql> use test;
    Database changed
    mysql> select * from user;
    Empty set (0.00 sec)
    
    mysql>
    

    如果我们不退出, 而是输入COMMIT; 时, 那么此时数据才真正保存到 MySQL 中:

    mysql> commit;
    Query OK, 0 rows affected (0.02 sec)
    
    mysql> show binlog events;
    +------------------+-----+----------------+-----------+-------------+---------------------------------------+
    | Log_name         | Pos | Event_type     | Server_id | End_log_pos | Info                                  |
    +------------------+-----+----------------+-----------+-------------+---------------------------------------+
    | mysql-bin.000001 |   4 | Format_desc    |         1 |         123 | Server ver: 5.7.13-log, Binlog ver: 4 |
    | mysql-bin.000001 | 123 | Previous_gtids |         1 |         154 |                                       |
    | mysql-bin.000001 | 154 | Anonymous_Gtid |         1 |         219 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'  |
    | mysql-bin.000001 | 219 | Query          |         1 |         291 | BEGIN                                 |
    | mysql-bin.000001 | 291 | Table_map      |         1 |         342 | table_id: 108 (test.user)             |
    | mysql-bin.000001 | 342 | Write_rows     |         1 |         394 | table_id: 108 flags: STMT_END_F       |
    | mysql-bin.000001 | 394 | Xid            |         1 |         425 | COMMIT /* xid=58 */                   |
    +------------------+-----+----------------+-----------+-------------+---------------------------------------+
    7 rows in set (0.00 sec)
    

    从上面的操作中我们可以看到, 当禁用了 AUTOCOMMIT 后, 我们对数据库的写入操作并不会实际落地到数据库中, 除非我们显示地提交事务.

    接下来, 我们使能 AUTOCOMMIT, 再次进行相同的操作.

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

    然后插入一个数据:

    mysql> INSERT INTO user (`id`, `name`, `age`) VALUES (1, 'xys', 18);
    

    接着查看数据:

    mysql> select * from user;
    +----+------+------+
    | id | name | age  |
    +----+------+------+
    |  1 | xys  |   18 |
    +----+------+------+
    1 row in set (0.00 sec)
    

    数据库中可以查询到这条数据了.
    我们再次查看 binlog, 对比一下和禁用 AUTOCOMMIT 时有什么差别:

    mysql> show binlog events;
    +------------------+-----+----------------+-----------+-------------+---------------------------------------+
    | Log_name         | Pos | Event_type     | Server_id | End_log_pos | Info                                  |
    +------------------+-----+----------------+-----------+-------------+---------------------------------------+
    | mysql-bin.000001 |   4 | Format_desc    |         1 |         123 | Server ver: 5.7.13-log, Binlog ver: 4 |
    | mysql-bin.000001 | 123 | Previous_gtids |         1 |         154 |                                       |
    | mysql-bin.000001 | 154 | Anonymous_Gtid |         1 |         219 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'  |
    | mysql-bin.000001 | 219 | Query          |         1 |         291 | BEGIN                                 |
    | mysql-bin.000001 | 291 | Table_map      |         1 |         342 | table_id: 108 (test.user)             |
    | mysql-bin.000001 | 342 | Write_rows     |         1 |         394 | table_id: 108 flags: STMT_END_F       |
    | mysql-bin.000001 | 394 | Xid            |         1 |         425 | COMMIT /* xid=292 */                  |
    +------------------+-----+----------------+-----------+-------------+---------------------------------------+
    7 rows in set (0.00 sec)
    

    我们看到, 和禁用 AUTOCOMMIT 不同的是, 使能 AUTOCOMMIT 时, 每个写操作都会进行事务的提交. 即上面的 insert 操作等效为:

    BEGIN;
    INSERT INTO user (`id`, `name`, `age`) VALUES (1, 'xys', 18);
    COMMIT;
    
  • 相关阅读:
    [Swift]LeetCode646. 最长数对链 | Maximum Length of Pair Chain
    [Swift]LeetCode645. 错误的集合 | Set Mismatch
    [Swift]LeetCode643. 子数组最大平均数 I | Maximum Average Subarray I
    [Swift]LeetCode641. 设计循环双端队列 | Design Circular Deque
    [Swift]LeetCode640. 求解方程 | Solve the Equation
    [Swift]LeetCode639. 解码方法 2 | Decode Ways II
    [Swift]LeetCode638. 大礼包 | Shopping Offers
    [Swift]LeetCode637. 二叉树的层平均值 | Average of Levels in Binary Tree
    [Swift]LeetCode636. 函数的独占时间 | Exclusive Time of Functions
    (4.2)动态管理视图DMV
  • 原文地址:https://www.cnblogs.com/xys1228/p/6035473.html
Copyright © 2011-2022 走看看