【MySQL】10.事务管理
1. 事务的引入
首先我们需要知道CURD操作不加控制会产生什么问题:
为了解决上面的问题,CURD需要满足如下条件:
2. 事务的概念
事务就是一组DML语句组成,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部失败。然而一个 MySQL 数据库可能会有大量的事务同时向MySQL服务器发起事务处理请求,每条事务至少一条 SQL,这样如果大家都访问同样的表数据,在不加保护的情况,就绝对会出现问题;甚至,由多条 SQL 构成的事务也会存在执行到一半出错或者不想再执行的情况,诸如这样的问题该怎么解决呢?
为了解决这个问题,一个完整的事务还需要满足ACID四个属性:
原子性(Atomicity):一个事务(transaction)中的所有操作要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完 全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Read uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化 ( Serializable )
持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
3. 事务的版本支持
mysql> show engines \G;
*************************** 1. row ***************************Engine: ARCHIVESupport: YESComment: Archive storage engine
Transactions: NOXA: NOSavepoints: NO
*************************** 2. row ***************************Engine: BLACKHOLESupport: YESComment: /dev/null storage engine (anything you write to it disappears)
Transactions: NOXA: NOSavepoints: NO
*************************** 3. row ***************************Engine: MRG_MYISAMSupport: YESComment: Collection of identical MyISAM tables
Transactions: NOXA: NOSavepoints: NO
*************************** 4. row ***************************Engine: FEDERATEDSupport: NOComment: Federated MySQL storage engine
Transactions: NULLXA: NULLSavepoints: NULL
*************************** 5. row ***************************Engine: MyISAMSupport: YESComment: MyISAM storage engine
Transactions: NO -- MyISAM 不支持事务XA: NOSavepoints: NO
*************************** 6. row ***************************Engine: PERFORMANCE_SCHEMASupport: YESComment: Performance Schema
Transactions: NOXA: NOSavepoints: NO
*************************** 7. row ***************************Engine: InnoDBSupport: DEFAULTComment: Supports transactions, row-level locking, and foreign keys
Transactions: YES -- InnoDB 支持事务XA: YESSavepoints: YES
*************************** 8. row ***************************Engine: MEMORYSupport: YESComment: Hash based, stored in memory, useful for temporary tables
Transactions: NOXA: NOSavepoints: NO
*************************** 9. row ***************************Engine: CSVSupport: YESComment: CSV storage engine
Transactions: NOXA: NOSavepoints: NO
9 rows in set (0.00 sec)ERROR:
No query specified
可以看出在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务, MyISAM 不支持。
4. 事务的提交方式
接下来我们使用下面这张表来测试:
mysql> create table account(-> id int primary key,-> name varchar(50) not null default '',-> balance decimal(10,2) not null default 0.0-> )engine = innodb;
Query OK, 0 rows affected (0.05 sec)
接下来为了验证提交方式,我们先将mysql的默认隔离级别设置为读未提交:
mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
4.1 自动提交
mysql> set autocommit = 1;
Query OK, 0 rows affected (0.00 sec)
4.2 手动提交
mysql> set autocommit = 0;
Query OK, 0 rows affected (0.00 sec)
结论:
• 只要输入begin或者start transaction,事务便必须要通过commit提交,才会持久化,与是否设置set autocommit 无关。
• 事务可以手动回滚,同时,当操作异常,MySQL会自动回滚。
• 对于 InnoDB 每一条 SQL 语言都默认封装成事务,自动提交。(select有特殊情况,因为 MySQL 有 MVCC )
• 如果没有设置保存点也可以回滚,只能回滚到事务的开始。直接使用 rollback(前提是事务还没有提交),也可以选择回退到哪个保存点;如果一个事务被提交了(commit),则不可以回退(rollback)。
5. 事务的隔离级别
5.1 设置和查看隔离级别
mysql> select @@global.transaction_isolation;
+--------------------------------+
| @@global.transaction_isolation |
+--------------------------------+
| READ-UNCOMMITTED |
+--------------------------------+
1 row in set (0.00 sec)mysql> select @@session.transaction_isolation;
+---------------------------------+
| @@session.transaction_isolation |
+---------------------------------+
| READ-UNCOMMITTED |
+---------------------------------+
1 row in set (0.00 sec)mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-UNCOMMITTED |
+-------------------------+
1 row in set (0.00 sec)mysql> set global transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)mysql> set transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)
5.2 读未提交[Read Uncommitted]
在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果。实际生产中不可能使用这种隔离级别的,但是相当于没有任何隔离性,也会有很多并发问题,如脏读,幻读,不可重复读等。

5.3 读提交[Read Committed]
该隔离级别是大多数数据库的默认的隔离级别(不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,如果多次 select, 可能得到不同的结果。

5.4 可重复读[Repeatable Read]
这是 MySQL 默认的隔离级别,它确保同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行。但是会有幻读问题。

5.5 串行化[Serializable]
这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突, 从而解决了幻读的问题。它在每个读的数据行上面加上共享锁,但是可能会导致超时和锁竞争(这种隔离级别太极端,实际生产基本不使用)。
隔离级别如何实现?
隔离基本都是通过锁实现的,不同的隔离级别使用的锁是不同的。常见有表锁,行锁,读锁,写锁,间隙锁(GAP),Next-Key锁(GAP+行锁)等。
总结:
• 隔离级别越严格,安全性越高,但数据库的并发性能也就越低,往往需要在两者之间找一个平衡点。
• 不可重复读的重点是修改和删除:同样的条件, 你读取过的数据,再次读取出来发现值不一样了
• 幻读的重点在于新增:同样的条件, 第1次和第2次读出来的记录数不一样
• mysql 默认的隔离级别是可重复读,一般情况下不要修改
• 上面的例子可以看出,事务也有长短事务这样的概念。事务间互相影响,指的是事务在并行执行的 时候,即都没有commit的时候,影响会比较大。
6. 数据库的并发场景
6.1 读-写并发
读-写并发有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读。
多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制。MVCC 可以为数据库解决1)在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能;2)同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题。
• MVCC的3个记录隐藏列字段
• DB_TRX_ID:6 byte,最近修改( 修改/插入)事务ID,记录创建这条记录/最后一次修改该记录的事务
• ID DB_ROLL_PTR: 7 byte,回滚指针,指向这条记录的上一个版本
• DB_ROW_ID: 6 byte,隐含的自增ID(隐藏主键),如果数据表没有主键, InnoDB 会自动以 DB_ROW_ID 产生一个聚簇索引
• undo 日志
MySQL 将来是以服务进程的方式,在内存中运行。我们之前所讲的所有机制:索引,事务,隔离性,日志等,都是在内存中完成的,即在 MySQL 内部的相关缓冲区中,保存相关数据,完成各种判断操作。然后在合适的时候,将相关数据刷新到磁盘当中的。所以,我们这里理解undo log,简单理解成 MySQL 中的一段内存缓冲区,用来保存日志数据。
模拟MVCC
上面的一个一个版本,我们可以称之为一个一个的快照。
• Read View
Read View 就是事务进行快照读操作的时候生产的读视图 (Read View),在该事务执行的快照读的那一刻会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时都会被分配一个ID,这个ID是递增的)。
Read View 在 MySQL 源码中,就是一个类,本质是用来进行可见性判断的。 即当我们某个事务执行快照读的时候,对该记录创建一个 Read View 读视图,用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的 undo log 里面的某个版本的数据。
class ReadView
{// 省略...
private:/** 高水位,大于等于这个ID的事务均不可见*/trx_id_t m_low_limit_id/** 低水位:小于这个ID的事务均可见 */trx_id_t m_up_limit_id;/** 创建该 Read View 的事务ID*/trx_id_t m_creator_trx_id;/** 创建视图时的活跃事务id列表*/ids_t m_ids;/** 配合purge,标识该视图不需要小于m_low_limit_no的UNDO LOG,如果其他视图也不需要,则可以删除小于m_low_limit_no的UNDO LOG*/trx_id_t m_low_limit_no;/** 标记视图是否被关闭*/bool m_closed;// 省略...
};
6.2 读-读并发
读-读并发不存在任何问题,因此不需要并发控制。
6.3 写-写并发
写-写并发有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失。