1、事务的概述:
在MySQL中,事务由单独单元的一条或多条SQL语句组成。在此单元中,每条MySQL语句是相互依赖的。而整个单独单元作为一个不可分割的整体,如果单元中一旦某条SQL语句执行失败或产生错误,整个单元将会回滚,所有收到影响的数据将返回到事务开始以前的状态;如果单元中的所有SQL语句都执行成功,则事务便被顺利执行。
MySQL的逻辑架构和存储引擎:

事务的存在周期:

2、事务的特性:
事务遵循ACID的测试
A:atomicity,原子性;原子的执行是一个全部发生或者全部失败的整体过程。在一个原子操作中,如果事务中的任何一条语句失败,那么前面执行的语句都将会被返回,以保证数据的整体性不被破坏。这在常用的系统应用中,为保证数据的安全性起到一定的作用。
C:consistency,一致性;在MySQL事务处理中,无论事务是完全成功或者是在中途因某些环节失败而导致失败,但事务使系统处于一致的状态时,其必须保证一致性。在MySQL中,一致性主要是由MySQL的日志机制处理,它记录数据库的所有变化,为事务恢复提供跟踪记录。如果系统在事务处理中间发生错误,MySQL恢复过程将使用这些日志发现事务是否已经完全执行或需要返回。一致性属性保证数据库从不返回一个未处理的事务。
I:isolation,隔离性;隔离性是指每个事务在自己的空间发生,与其他发生在系统中的事务隔离,而且事务的结果只在它完全被执行时才能看到。即使这样的一个系统中同时发生多个事务,隔离性也可以保证特定的事务在完成之前,其结果是不被公布的。当系统支持多个同时存在的用户和连接时,系统必须遵守隔离性原则,否则在执行过程中可能导致大量数据被破坏,隔离性保证每个事务完整的在其各自空间内被顺利执行,保证事务与事务之间不会相互冲突。
D:durability,持久性;一旦事务被提交,其所做的所有对数据的操作都会永久保存于数据库之中,MySQL的持久性是通过一条记录事务过程中系统变化的二进制事务日志文件来实现的。如果遇到硬件损坏或者系统的异常关机,系统下一次启动时,通过使用最后的备份和日志就可以恢复丢失的数据。
3、事务日志
MySQL 的日志有很多种,如二进制日志、错误日志、查询日志、慢查询日志等。此外 InnoDB 存储引擎还提供了两种事务日志:
1)redo log(重做日志)
2)undo log(回滚日志)
其中 redo log 用于保证事务持久性;undo log 则是事务原子性和隔离性实现的基础。
InnoDB 实现回滚,靠的是 undo log:
当事务对数据库进行修改时,InnoDB 会生成对应的 undo log。
如果事务执行失败或调用了 rollback,导致事务需要回滚,便可以利用 undo log 中的信息将数据回滚到修改之前的样子。
undo log 属于逻辑日志,它记录的是 sql 执行相关的信息。当发生回滚时,InnoDB 会根据 undo log 的内容做与之前相反的工作:
对于每个 insert,回滚时会执行 delete。
对于每个 delete,回滚时会执行 insert。
对于每个 update,回滚时会执行一个相反的 update,把数据改回去。
以 update 操作为例:当事务执行 update 时,其生成的 undo log 中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到 update 之前的状态。
InnoDB 作为 MySQL 的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘 IO,效率会很低。为此,InnoDB 提供了缓存(Buffer Pool),Buffer Pool 中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:
当从数据库读取数据时,会首先从 Buffer Pool 中读取,如果 Buffer Pool 中没有,则从磁盘读取后放入 Buffer Pool。当向数据库写入数据时,会首先写入 Buffer Pool,Buffer Pool 中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。
Buffer Pool 的使用大大提高了读写数据的效率,但是也带来了新的问题:如果 MySQL 宕机,而此时 Buffer Pool 中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。于是,redo log 被引入来解决这个问题:当数据修改时,除了修改 Buffer Pool 中的数据,还会在 redo log 记录这次操作;当事务提交时,会调用 fsync 接口对 redo log 进行刷盘。如果 MySQL 宕机,重启时可以读取 redo log 中的数据,对数据库进行恢复。
redo log 采用的是 WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到 Buffer Pool,保证了数据不会因 MySQL 宕机而丢失,从而满足了持久性要求。
4、事务的实现方式
常看自动提交事务功能是否打开:
MariaDB [(none)]> show global variables like "autocommit";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.01 sec)
关闭自动提交事务功能:
MariaDB [(none)]> set global autocommit=0;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> show global variables like "autocommit";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set (0.00 sec)
只有事务型存储引擎方能支持此类操作;关闭自动提交事务功能后,用户所做的操作都需要自行启动事务与结束事务
开启事务:
MariaDB [(none)]> start transaction;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]>
结束事务:
1)提交(所有的修改操作都会被保存在数据库中)
MariaDB [(none)]> commit;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]>
2)回滚(恢复到未修改数据库之前的状态)
MariaDB [(none)]> rollback;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]>
事务还支持回滚到指定的状态(savepoint)
例如:
MariaDB [aaa]> select * from test1;
+----+-------------+-----+
| id | name | age |
+----+-------------+-----+
| 1 | xiaoming | 10 |
| 2 | xiaohong | 11 |
| 3 | jiangyiyang | 12 |
+----+-------------+-----+
3 rows in set (0.00 sec)
修改xiaoming的age为100,并指定为状态1;
MariaDB [aaa]> start transaction; //开启事务;
Query OK, 0 rows affected (0.00 sec)
MariaDB [aaa]> update test1 set age=100 where id=1; //修改数据;
Query OK, 1 row affected (0.06 sec)
Rows matched: 1 Changed: 1 Warnings: 0
MariaDB [aaa]> savepoint save1; //指定保存状态的名称为save1;
Query OK, 0 rows affected (0.00 sec)
MariaDB [aaa]> select * from test1;
+----+-------------+-----+
| id | name | age |
+----+-------------+-----+
| 1 | xiaoming | 100 |
| 2 | xiaohong | 11 |
| 3 | jiangyiyang | 12 |
+----+-------------+-----+
3 rows in set (0.00 sec)
MariaDB [aaa]>
修改xaiohong的age为200;
MariaDB [aaa]> update test1 set age=200 where id=2; //修改数据;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
MariaDB [aaa]> savepoint save2; //回滚状态名称为save2;
Query OK, 0 rows affected (0.00 sec)
MariaDB [aaa]> select * from test1;
+----+-------------+-----+
| id | name | age |
+----+-------------+-----+
| 1 | xiaoming | 100 |
| 2 | xiaohong | 200 |
| 3 | jiangyiyang | 12 |
+----+-------------+-----+
3 rows in set (0.00 sec)
MariaDB [aaa]>
删除id为3的用户数据;并回滚到指定的状态
MariaDB [aaa]> delete from test1 where id=3; //删除id=3的用户数据;
Query OK, 1 row affected (0.00 sec)
MariaDB [aaa]> select * from test1;
+----+----------+-----+
| id | name | age |
+----+----------+-----+
| 1 | xiaoming | 100 |
| 2 | xiaohong | 200 |
+----+----------+-----+ //此时只有两个用户数据;
2 rows in set (0.00 sec)
MariaDB [aaa]>
回滚到save1状态并查看数据:
MariaDB [aaa]> rollback to save1; //回滚到save1状态;
Query OK, 0 rows affected (0.00 sec)
MariaDB [aaa]> select * from test1;
+----+-------------+-----+
| id | name | age |
+----+-------------+-----+
| 1 | xiaoming | 100 | //xaioming的age的确发生了改变;
| 2 | xiaohong | 11 | //xiaohong的age回到了之前的状态;
| 3 | jiangyiyang | 12 | //id为3的用户数据信息也回滚了;
+----+-------------+-----+
3 rows in set (0.00 sec)
MariaDB [aaa]>
MariaDB [aaa]> rollback; //回滚所有数据;
Query OK, 0 rows affected (0.00 sec)
MariaDB [aaa]> select * from test1; //数据库数据恢复到了未修改之前的状态;
+----+-------------+-----+
| id | name | age |
+----+-------------+-----+
| 1 | xiaoming | 10 |
| 2 | xiaohong | 11 |
| 3 | jiangyiyang | 12 |
+----+-------------+-----+
3 rows in set (0.00 sec)
MariaDB [aaa]>
5、事务的隔离级别
一般来说,隔离级别越低,系统开销越低,可支持的并发越高,但隔离性也越差。
脏读:当前事务(A)中可以读到其他事务(B)未提交的数据(脏数据),这种现象是脏读。
不可重复读:在事务 A 中先后两次读取同一个数据,两次读取的结果不一样,这种现象称为不可重复读。
脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据。
幻读:在事务 A 中按照某个条件先后两次查询数据库,两次查询结果的条数不同,这种现象称为幻读。
不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了。
隔离级别与读问题的关系如下:

查看隔离级别:
MariaDB [aaa]> show global variables like "tx_isolation";
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)
MariaDB [aaa]>
修改隔离级别:
MariaDB [aaa]> set tx_isolation='read-uncommitted';
Query OK, 0 rows affected (0.00 sec)
MariaDB [aaa]>
MariaDB [aaa]> show global variables like "tx_isolation";
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.01 sec)
InnoDB的隔离级别默认为可重复读;
可重复读级别解决脏读、不可重复读、幻读等问题,使用的是 MVCC:MVCC 全称 Multi-Version Concurrency Control,即多版本的并发控制协议。
MVCC 最大的优点是读不加锁,因此读写不冲突,并发性能好。InnoDB 实现 MVCC,多个版本的数据可以共存,主要是依靠数据的隐藏列(也可以称之为标记位)和 undo log。
其中数据的隐藏列包括了该行数据的版本号、删除时间、指向 undo log 的指针等等。
当读取数据时,MySQL 可以通过隐藏列判断是否需要回滚并找到回滚需要的 undo log,从而实现 MVCC;隐藏列的详细格式不再展开。
127

被折叠的 条评论
为什么被折叠?



