MySQL进阶:
事务
1.什么是事务?
数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,是一个不可分割的工作单位。事务管理的目标是完整性,一次中的若干操作要么都执行成功,要么都失败. 事务由事务开始与事务结束之间执行的全部数据库操作组成。
在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。 事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行, 要么全部不执行。
事务用来管理 insert,update,delete 语句。
2.事务的特性
一般来说,事务是必须满足 4 个条件(ACID):原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。
原子性:
一个事务中的所有操作,要么全部完成,要么全部不完成, 不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
隔离性:
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为 不同级别,包括读 未提交(Read uncommitted),读 以提交(read committed),可重复读(repeatable read)和串行化(Serializable).
持久性:
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。将数据持久化到硬盘上,不可回滚.
一致性:
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的数据必须完全符合所有的预设规则。比如不能出现转了 100,对方收到了 50 的情况。前面提到的原子性、持久性和隔离性,都是为了保证数据库状态的一致性。保证数据操作的完整性.
3.事务设置
默认情况下, MySQL 启用自动提交模式(变量 autocommit 为 ON)。这 意味着, 只要你执行 DML 操作的语句,MySQL 会立即隐式提交事务。
由于变量 autocommit 分会话系统变量与全局系统变量,所以查询的时候, 最好区别是会话系统变量还是全局系统变量。
查看 autocommit 模式:
SHOW GLOBAL VARIABLES LIKE ‘autocommit’;
MYSQL 事务处理主要有两种方法:
1、用 BEGIN, ROLLBACK, COMMIT 来实现
BEGIN; / START TRANSACTION; 开始一个事务 ;
ROLLBACK 事务回滚 ;
COMMIT 事务确认.
2.直接用 SET 来改变 MySQL 的自动提交模式:
SET GLOBAL autocommit=0; 禁止自动提交
SET GLOBAL autocommit=1;开启自动提交
4.事务隔离级别
只有 InnoDB 支持事务,所以这里说的事务隔离级别是指 InnoDB 下的事务隔离级别。
查看隔离级别:
SELECT @@global.transaction_isolation,@@transaction_isolation;
设置隔离级别:
SET GLOBAL TRANSACTION ISOLATION LEVEL 隔离级别名称;
Mysql 数据提供四种不同级别的隔离级别 (读 未提交,;读 已提交;可重复读;串行化) , 实际开发中可以根据不同的需要场景选择不同的隔离级别,除了串行级别以外其他级别都会存在某种问题.
1.读 未提交:(read uncommitted)
一个事务可以读到另一个事务还未提交的数据.
问题:可能会出现脏读,幻读,不可重复读问题;
2.读 已提交:(read committed)
一个事务只能读取另一个事务已经提交的修改。
问题:避免了脏读,仍然存在不可以重复读和幻读问题。
3.可重复读:(repeatable read 是MySQL默认隔离级别)
同一个事务中多次读取相同的数据返回的结果是一样的。
问题:其避免了脏读和不可重复读问题,但是除 InnoDB 外幻读依然存在。
4.串行化(serializable):
Serializable 是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读。
脏读: (读取未提交数据)
A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的就是脏数据,
这种情况常发生于转账与取款操作中:
不可重复读: (前后多次读取,数据内容不一致)
事务A在执行读取操作,由整个事务A比较大,前后读取同一条数据需要经历很长的时间.而在事务A第一次读取数据,比如此时读取了小明的年龄为20岁,事务B更改操作,将小明的年龄更改为30岁,此时事务A第二次读到小明的年龄的时候,发现其年龄为30岁,和之前的年龄不一样了,那就是数据不重复了,系统不可以读取到重复的数据,成为不可重复读.
幻读:(前后多次读取,数据总量不一致)
事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读。
5.事务实现原理
MySQL 的日志有很多种,如二进制日志、错误日志、查询日志、慢查询日 志等,此外 InnoDB 存储引擎还提供了两种事务日志: redolog(重做日志) 和 undolog(回滚日志)。其中 redolog 用于保证事务持久性;undolog 则是事务原子性和隔离性实现的基础。
原子性实现:
实现原子性的关键,是当事务回滚时能够撤销所有已经成功执行的 sql 语 句。InnoDB 实现回滚,靠的是 undolog:当事务对数据库进行修改时,InnoDB 会生成对应的 undolog;如果事务执行失败或调用了 rollback,导致事务需要回滚,便可以利用 undolog 中的信息将数据回滚到修改之前的样子。
undolog 属于逻辑日志,它记录的是 sql 执行相关的信息。当发生回滚时, InnoDB 会根据 undo log 的内容做与之前相反的操作:对于每个 insert,回滚时会执行 delete;对于每个 delete,回滚时会执行 insert;对于每个 update, 回滚时会执行一个相反的 update,把数据改回去.
持久性实现:
redolog 叫做重做日志,是保证事务持久性的重要机制。当 mysql 服务器意外崩溃或者宕机后,保证已经提交的事务,确定持久化到磁盘中的一种措施。
innodb 是以页为单位来管理存储空间的,任何的增删改差操作最终都会 操作完整的一个页,会将整个页加载到 buffer pool 中,然后对需要修改的记录进行修改,修改完毕不会立即刷新到磁盘,而且仅仅修改了一条记录,刷新一个完整的数据页的话过于浪费了。但是如果不立即刷新的话,数据此时还在内存中, 如果此时发生系统崩溃最终数据会丢失的,因此权衡利弊,引入了 redolog, 也就是说,修改完后,不立即刷新,而是记录一条日志,日志内容就是记录哪个页面,多少偏移量,什么数据发生了什么变更。这样即使系统崩溃,再恢复后, 也可以根据 redo 日志进行数据恢复。另外,redolog 是循环写入固定的文件, 是顺序写入磁盘的。
mysql对数据操作时,并不是立即将数据写入磁盘,这样io多,效率低,
数据又不能一直保持在缓存中,万一服务器宕机,那么数据就不存在了.
因此mysql提供了redolog日志,可以将数据先暂时保持在日志中,记录哪些数据发生了修改,定期将日志数据写到磁盘.
隔离性实现:
锁+MVCC
隔离级别实现(MVCC):
MVCC(多版本 并发 控制 Multi-Version Concurrent Control),是 MySQL 提高性能的一种方式,配合 Undolog 和版本链,让不同事务的读-写、 写-读操作可以并发执行,从而提升系统性能.
InnoDB 的 MVCC 是通过在每行记录后面保存两个隐藏的列来实现的。一个保存了行的事务 ID(DB_TRX_ID),一个保存了行的回滚指针(DB_ROLL_PT)
db_trx_id:每次对某条聚簇索引记录进行改动时,都会把对应的事务 id 赋值给 trx_id 隐藏列.
db_roll_pt:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undolog 日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息.
每个事物在对表记录操作时,会将之前的数据保存在undolog日志中,表中有两个隐藏列(事物id,版本id),
每次最新版本都会记录当前操作事物id,上一个版本的id,形成一个版本链.
ReadView
对于使用READ UNCOMMITTED (读 未提交) 隔离级别的事务来说,直接读取记录的最新版本就好了,对于使用 SERIALIZABLE(串行化) 隔离级别的事务来说,使用加锁的方式来访问记录。
对于使用 READ COMMITTED(读 已提交) 和 REPEATABLE READ (可重复读) 隔离级别的事务来说,就需要用到我们上边所说的版本链了,核心问题就是:需要判断一下版本链中的哪个版本是当前事务可见的。
ReadView: 主要包含当前系统中还有哪些活跃的读写事务,把它们的事务 id 放到一个列表中.
主要包含 4 个比较重要的内容:
m_ids :表示在生成 ReadView 时当前系统中活跃的读写事务的事务 id 列表。
min_trx_id :表示在生成 ReadView 时当前系统中活跃的读写事务中最小 的事务 id ,也就是 m_ids 中的最小值。
max_trx_id :表示生成 ReadView 时系统中应该分配给下一个事务的 id值。注意 max_trx_id 并不是 m_ids 中的最大值。
creator_trx_id :表示生成该 ReadView 的事务的 事务 id 。
readview中存储一些当前操作的活跃事物对象id,记录当前事务id, 活跃事物id,最大,最小.
关注点是从readviw中判断应该获取版本链中的哪条记录出来.
对于看到的 读已提交 为什么其他事务提交后 就可以读到最新的版本?
因为每次读的时候就会产生一个readview 读到最新的数据 称为当前读.
对于可重复读
是在第一次读取数据时,就会生成readview, 这样之后再次读时与第一次的版本是一致的, 称为快照读.