MySQL事务实现原理
Posted less is more
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL事务实现原理相关的知识,希望对你有一定的参考价值。
事务是什么?
首先思考一个问题,事务是什么?以下是事务的相关解释
MySQL中的事务是一种用于确保数据库操作的完整性和一致性的机制。事务处理具有以下四个基本特性,通常被称为ACID特性:
原子性(Atomicity):原子性是指事务中的所有操作要么全部完成,要么全部不完成。事务中的操作不可分割,如果其中一个操作失败,整个事务都会回滚。这意味着事务的所有操作要么一起提交,要么一起取消。
一致性(Consistency):一致性是指事务执行前后,数据库从一个一致性状态转换到另一个一致性状态。换句话说,事务应确保数据库的完整性约束得到维护。例如,如果有一个完整性约束要求一个账户的余额不能为负数,那么在事务执行之后,这个约束仍然应该得到满足。
隔离性(Isolation):隔离性是指在并发环境中,一个事务的执行不应受到其他事务的干扰。事务在执行过程中所做的修改对其他事务来说是不可见的,直到该事务成功提交。隔离性的实现可以通过多种隔离级别来实现,包括读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
持久性(Durability):持久性是指事务一旦提交,对数据库所做的修改将会永久保存。即使在系统故障或重启的情况下,已提交的事务对数据库的更改也不会丢失。
总之,MySQL事务的特性(ACID特性)旨在确保数据库操作的安全、可靠和一致。通过使用事务,我们可以保证数据库始终处于一致性状态,避免因并发访问和系统故障导致的数据损坏。
redo log的实现原理,以及使用场景
MySQL的redo log(重做日志)是一种用于确保事务的持久性和恢复能力的日志记录机制。它是InnoDB存储引擎的一个重要组件。下面我们将详细介绍redo log的底层原理和使用场景。
底层原理:
当一个事务开始执行时,InnoDB会为该事务分配一个事务ID。
在事务执行过程中,所有的数据修改操作(例如插入、更新和删除)都会在内存中的缓冲池(buffer pool)进行。
同时,所有的数据修改操作也会被记录到redo log buffer(重做日志缓冲区)中。这些记录包含了足够的信息,可以在需要时重新执行(重做)这些操作。
当事务提交时,InnoDB会将redo log buffer中的记录写入磁盘上的redo log文件(也称为重做日志文件或重做日志磁盘)中。这个过程叫做flush(刷新)。
磁盘上的redo log文件是循环使用的,当一个redo log文件被写满后,InnoDB会从头开始写入下一个redo log文件。
使用场景:
崩溃恢复(Crash Recovery):在数据库系统崩溃时,内存中的缓冲池可能包含未写入磁盘的数据。这时,MySQL可以利用redo log文件重新执行(重做)这些修改操作,确保数据的持久性。这个过程称为崩溃恢复。
缓冲池写入优化:通过使用redo log,InnoDB可以在事务提交时先将日志记录写入磁盘,而不是立即将缓冲池中的数据写入磁盘。这样,InnoDB可以在稍后合适的时间批量写入磁盘,从而提高I/O性能。
备份与恢复:在某些场景下,如基于物理备份的增量备份,可以利用redo log记录在备份时段内的数据变更,从而在恢复时重放这些变更,实现数据的一致性恢复。
总之,MySQL的redo log是InnoDB存储引擎的一个关键组件,它通过记录数据修改操作,确保在系统崩溃时可以恢复数据的一致性,同时还可以优化缓冲池的写入性能。
undo log的实现原理,以及使用场景
MySQL的undo log(回滚日志)是InnoDB存储引擎用于实现多版本并发控制(MVCC)和事务回滚的一种日志记录机制。接下来我们将详细介绍undo log的底层原理和使用场景。
底层原理:
当一个事务开始执行时,InnoDB会为该事务分配一个事务ID。
在事务执行过程中,所有的数据修改操作(例如插入、更新和删除)都会在内存中的缓冲池(buffer pool)进行。同时,对于每个数据修改操作,InnoDB会将修改前的数据版本记录到undo log中。
Undo log以段(segment)的形式存储在InnoDB的系统表空间(system tablespace)中,每个事务都有一个或多个专用的undo log段。
如果事务执行失败,或者需要执行ROLLBACK操作,InnoDB可以利用undo log中的记录来还原数据的原始状态。即执行数据回滚。
当事务提交后,如果没有其他事务需要访问这些旧版本数据,InnoDB会在适当的时候回收和重用这些undo log空间。
使用场景:
事务回滚:在执行事务过程中,如果遇到错误或者用户主动执行ROLLBACK操作,InnoDB可以利用undo log回滚事务中的数据修改操作,还原数据到事务开始之前的状态。
多版本并发控制(MVCC):在InnoDB中,undo log被用于实现MVCC,它允许不同事务访问数据的不同快照版本。当一个事务需要读取数据时,InnoDB可以通过undo log生成一个适当的数据版本,从而确保每个事务看到的数据是一致的。这样可以在不加锁的情况下实现并发事务的隔离性。
快照读(consistent read):当执行一致性读操作时,InnoDB会利用undo log生成数据的某个历史版本,这样就可以在不影响其他事务的情况下获得数据的稳定快照。
总之,MySQL的undo log是InnoDB存储引擎的一个关键组件,它通过记录数据修改前的旧版本,实现了事务回滚和多版本并发控制,提高了数据库的并发性能。
MVCC的实现原理,以及使用场景
MySQL中的MVCC(多版本并发控制)是一种用于实现事务隔离性的机制。MVCC允许多个事务同时访问数据库中的数据,而无需加锁。在InnoDB存储引擎中,MVCC通过使用undo log(回滚日志)实现。下面我们将详细介绍MVCC的底层原理和使用场景。
底层原理:
当一个事务开始时,它会获得一个唯一的事务ID。该ID用于区分不同的事务。
在事务执行过程中,每当对一行数据进行修改(例如插入、更新或删除)时,InnoDB会在undo log中记录该行数据修改前的版本。同时,InnoDB会为每行数据添加两个额外的隐藏字段:DB_TRX_ID和DB_ROLL_PTR。DB_TRX_ID用于记录最后一次修改该行数据的事务ID,DB_ROLL_PTR指向相应的undo log记录。
当一个事务需要读取一行数据时,InnoDB会根据该事务的ID和DB_TRX_ID进行比较。如果该事务的ID大于DB_TRX_ID(即该事务在修改事务之后开始),InnoDB会检查该行数据是否有对应的undo log记录。如果有,则InnoDB会使用undo log中的记录生成一个适当的历史版本,并返回给事务。这样,事务就可以看到一致性的数据快照,而不受其他并发事务的影响。
使用场景:
事务隔离:MVCC允许多个事务同时访问数据库,而无需对数据进行加锁。这样,在高并发场景下,事务之间可以独立运行,提高了数据库的性能。
非锁定读操作:在InnoDB存储引擎中,MVCC允许执行非锁定读操作(如SELECT)。这意味着读操作可以在不加锁的情况下进行,减少了锁争用,提高了性能。
快照读(consistent read):当事务需要执行一致性读操作时,InnoDB会利用MVCC机制生成数据的某个历史版本。这样,事务可以看到一个数据的稳定快照,而不受其他事务的影响。
总之,MySQL的MVCC是一种用于实现事务隔离性的机制,它允许多个事务同时访问数据库中的数据,提高了数据库在高并发场景下的性能。在InnoDB存储引擎中,MVCC通过使用undo log实现,确保事务能够看到一致性的数据快照。
MYSQL相关的锁技术原理,以及使用场景
MySQL中的锁技术主要包括以下几种:
- 全局锁(Global Lock):
全局锁会锁定整个数据库,通常用于对整个数据库进行备份或者升级操作。使用FLUSH TABLES WITH READ LOCK
命令可以实现全局锁。全局锁的使用场景较少,因为它会影响到所有用户的操作。 - 表锁(Table Lock):
表锁会锁定整个表,使得其他用户无法对该表进行写操作。表锁的使用场景包括:MyISAM存储引擎的读写操作、ALTER TABLE等DDL操作。表锁的优点是简单易用,但在高并发场景下可能会成为性能瓶颈。 - 行锁(Row Lock):
行锁是最细粒度的锁,它只锁定被操作的行。InnoDB存储引擎支持行锁。行锁的使用场景包括:事务中的读写操作。行锁可以有效地减少锁冲突,提高并发性能,但实现起来相对复杂。 - 乐观锁(Optimistic Lock):
乐观锁并不是MySQL本身提供的锁机制,而是一种编程思想。乐观锁假设数据在一般情况下不会造成冲突,因此在读取数据时不加锁,只在更新数据时检查是否有冲突。乐观锁适用于读多写少的场景,可以通过版本号或者时间戳实现。 - 悲观锁(Pessimistic Lock):
悲观锁与乐观锁相反,它假设数据很容易发生冲突,因此在读取数据时就加锁。悲观锁适用于写多读少的场景。在MySQL中,可以通过SELECT ... FOR UPDATE
语句实现悲观锁。 - 间隙锁(Gap Lock):
间隙锁是InnoDB存储引擎特有的一种锁,它锁定的是一个范围,而不是具体的行。间隙锁主要用于防止幻读(Phantom Read)问题。间隙锁的使用场景包括:事务隔离级别为可重复读(REPEATABLE READ)或串行化(SERIALIZABLE)时的范围查询。 - 临键锁(Next-Key Lock):
临键锁是InnoDB存储引擎的另一种锁,它是行锁和间隙锁的结合。临键锁可以锁定一行数据以及该行数据之前的间隙,从而防止幻读问题。临键锁的使用场景与间隙锁类似,主要用于事务隔离级别为可重复读或串行化时的范围查询。
总结:
MySQL中的锁技术有多种,它们在不同的使用场景下有各自的优缺点。在实际应用中,需要根据业务需求和性能要求选择合适的锁机制。
分别从事务的几个特性入手,分析事务实现的原理
原子性
MySQL事务的原子性(Atomicity)是指一个事务中的所有操作要么全部执行成功,要么全部不执行。如果事务中的某个操作失败,则整个事务需要回滚到开始状态。在InnoDB存储引擎中,事务原子性主要通过undo log(回滚日志)来实现。
实现原理:
- 当一个事务开始时,InnoDB会为该事务分配一个唯一的事务ID。
- 在事务执行过程中,每当对一行数据进行修改(例如插入、更新或删除)时,InnoDB会在undo log中记录该行数据修改前的版本。
- 如果事务执行过程中发生错误,或者用户主动发起回滚操作(ROLLBACK),InnoDB会利用undo log中的记录将事务中已执行的操作逐个回滚,还原数据到事务开始之前的状态。
- 如果事务执行成功并提交(COMMIT),InnoDB会将事务中的修改操作永久保存到数据库,并释放对应的undo log空间。
具体例子: 假设我们有一个银行转账事务,从账户A向账户B转账1000元。这个事务包含两个操作:从账户A扣除1000元,向账户B增加1000元。
- 事务开始:分配一个唯一的事务ID。
- 从账户A扣除1000元:InnoDB会记录账户A修改前的余额到undo log中,并在内存的缓冲池中执行扣款操作。
- 向账户B增加1000元:InnoDB会记录账户B修改前的余额到undo log中,并在内存的缓冲池中执行存款操作。
- 在此过程中,如果发生错误(例如余额不足),InnoDB会利用undo log回滚事务,将账户A和账户B的余额还原到事务开始前的状态。
- 如果操作成功,事务提交:InnoDB会将缓冲池中的修改操作永久保存到数据库,并释放对应的undo log空间。
通过这个例子,我们可以看到MySQL事务原子性的实现原理。事务中的所有操作要么全部成功,要么全部回滚,确保了数据的一致性和完整性。在InnoDB存储引擎中,这主要是通过undo log实现的。
隔离性
MySQL事务的隔离性(Isolation)是指在并发环境中,事务之间的操作相互隔离,一个事务不会看到其他事务未提交的数据。在InnoDB存储引擎中,事务隔离性主要通过多版本并发控制(MVCC)和锁机制来实现。
实现原理:
- 多版本并发控制(MVCC):InnoDB通过为每个行记录添加两个额外的隐藏字段(创建时间和过期时间,对应事务的ID)来实现MVCC。通过这两个字段,InnoDB可以判断某个行记录在某个事务中是否可见。MVCC允许不同事务读取同一行数据的不同版本,从而避免了读操作的阻塞。
- 锁机制:InnoDB支持行级锁(row-level locking)和表级锁(table-level locking)。通过锁机制,InnoDB可以防止多个事务同时修改同一行数据。锁类型包括共享锁(S锁,允许多个事务读取同一行数据)和排他锁(X锁,只允许一个事务修改数据)。
在不同的隔离级别下,MySQL使用不同的策略来实现事务隔离性:
- 读未提交(READ UNCOMMITTED):一个事务可以读取到其他事务未提交的数据。这个级别很少使用,因为可能导致脏读(Dirty Read)等问题。
- 读已提交(READ COMMITTED):一个事务只能读取到其他事务已提交的数据。这个级别通过MVCC实现,避免了脏读问题,但仍可能出现不可重复读(Non-repeatable Read)。
- 可重复读(REPEATABLE READ):在同一个事务中,对同一行数据的多次读取结果是一致的。这个级别是InnoDB的默认隔离级别。通过MVCC和锁机制,避免了脏读和不可重复读问题,但仍可能出现幻读(Phantom Read)。
- 串行化(SERIALIZABLE):事务顺序执行,一个事务必须等待其他事务完成才能开始。这个级别通过行级锁实现,避免了脏读、不可重复读和幻读问题,但并发性能较差。
具体例子:
假设我们有一个商品库存表,包含商品ID、库存数量等字段。现在有两个并发执行的事务T1和T2,它们分别执行以下操作:
- 事务T1:向商品库存表中插入一条新商品记录(商品ID为1,库存数量为10)。
- 事务T2:查询商品库存表中商品ID为1的记录。
为了保证事务隔离性,我们需要确保T2在查询商品库存表时,不会看到T1未提交的插入操作。以下是实现隔离性的方法:
- 使用MVCC: a. 当事务T1开始时,InnoDB会为其分配一个唯一的事务ID。 b. 当T1插入新商品记录时,InnoDB会在内存中的缓冲池执行插入操作,并将该操作记录到undo log(用于回滚)。 c. 当事务T2开始时,InnoDB同样会为其分配一个唯一的事务ID。 d. 当T2查询商品ID为1的记录时,InnoDB会检查T1的事务ID以及商品库存表中商品ID为1的记录的隐藏版本信息。如果T1尚未提交,那么T2将无法看到商品ID为1的记录。这样就实现了事务隔离性。
- 使用锁机制: a. 当事务T1向商品库存表中插入新商品记录时,InnoDB可能会为该记录加上一个行锁。 b. 在T1提交之前,T2无法访问该记录。这样,在T1完成并释放锁后,T2才能查询商品ID为1的记录。这种情况下,事务隔离性得以实现。
通过这个例子,我们可以看到MySQL事务隔离性的底层实现原理。通过多版本并发控制(MVCC)和锁机制,我们可以确保事务之间的操作相互隔离,保证数据的一致性和完整性。
持久性
MySQL事务的持久性(Durability)是指一旦事务提交成功,对数据的修改将被永久保存到数据库中,即使系统崩溃或发生故障也不会丢失。在InnoDB存储引擎中,事务持久性主要通过redo log(重做日志)来实现。
实现原理:
- 当一个事务开始时,InnoDB会为该事务分配一个唯一的事务ID。
- 在事务执行过程中,所有的数据修改操作(例如插入、更新和删除)都会在内存中的缓冲池(buffer pool)进行。同时,所有的数据修改操作也会被记录到redo log buffer(重做日志缓冲区)中。
- 当事务提交时,InnoDB会将redo log buffer中的记录写入磁盘上的redo log文件中。这个过程叫做flush(刷新)。这样就确保了即使系统崩溃,磁盘上的redo log文件仍然包含了事务中所有的修改操作。
- 在系统恢复时,InnoDB会根据redo log文件中的记录重做(redo)事务中的所有修改操作,从而确保数据的持久性。
具体例子:
假设我们有一个添加新用户的事务,包括以下操作:向用户表中插入一条新的用户记录,向地址表中插入一条新的地址记录。
- 事务开始:分配一个唯一的事务ID。
- 向用户表插入新记录:InnoDB会在内存的缓冲池中执行插入操作,并将该操作记录到redo log buffer中。
- 向地址表插入新记录:InnoDB会在内存的缓冲池中执行插入操作,并将该操作记录到redo log buffer中。
- 事务提交:InnoDB将redo log buffer中的记录写入磁盘上的redo log文件中。
- 如果此时系统崩溃,数据库在恢复时会根据redo log文件中的记录重做事务中的所有修改操作,从而确保数据的持久性。
通过这个例子,我们可以看到MySQL事务持久性的实现原理。通过将事务中的所有修改操作记录到磁盘上的redo log文件,InnoDB确保了即使在系统崩溃的情况下,事务中的修改操作仍然可以恢复,实现了数据的持久性。
一致性
在MySQL中,事务一致性(Consistency)指事务执行前后数据库都必须保持一致性状态。这意味着事务的执行不能破坏数据库的约束和完整性规则。在InnoDB存储引擎中,事务一致性主要通过ACID属性(原子性、一致性、隔离性和持久性)的实现来保证。
实现原理:
- 原子性(Atomicity):事务中的所有操作要么全部执行成功,要么全部不执行。这通过undo log(回滚日志)来实现。如果事务执行失败或需要回滚,InnoDB会利用undo log还原数据到事务开始前的状态。
- 隔离性(Isolation):事务之间的操作相互隔离,一个事务不会看到其他事务未提交的数据。这通过多版本并发控制(MVCC)和锁机制来实现。
- 持久性(Durability):一旦事务提交成功,对数据的修改将被永久保存到数据库中。这通过redo log(重做日志)来实现。
通过上述原理,事务一致性得以实现。当事务开始时,数据库处于一致性状态。事务中的操作会按照原子性、隔离性和持久性的要求执行,确保事务完成后,数据库仍然处于一致性状态。
具体例子:
假设我们有一个银行转账事务,从账户A向账户B转账1000元。这个事务包含两个操作:从账户A扣除1000元,向账户B增加1000元。要保证事务一致性,我们需要满足以下条件:
- 原子性:转账操作要么全部成功(账户A扣款成功,账户B收款成功),要么全部失败(如账户A余额不足)。如果转账失败,事务需要回滚,将数据还原到事务开始前的状态。
- 隔离性:在转账过程中,其他事务不应该看到中间状态(如仅看到账户A扣款,而未看到账户B收款)。通过MVCC和锁机制,我们可以确保事务之间的操作相互隔离。
- 持久性:一旦转账事务成功提交,账户A和账户B的余额修改应永久保存到数据库中。通过redo log,我们可以确保即使系统崩溃,事务的修改仍然可以恢复。
通过这个例子,我们可以看到MySQL事务一致性的实现原理。通过确保事务的原子性、隔离性和持久性,我们可以确保事务执行前后数据库的一致性得以维护。
Mysql事务隔离级别及ACID实现原理
1. MySql事务简介
事务是一个最小的不可再分的工作单元,通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)
事务只和DML语句(sql语句中的curd)有关,也可以说DML语句才有事务
2. 事务的用法
mysql默认开启了自动提交,在执行insert,update,delete语句时候每一条sql语句就是一个事务
(1) 事务涉及的关键字
① 用begin,rollback,commit来实现事务
开启事务使用 begin; 或 start transaction;
回滚:rollback;
回滚后,事务结束,但是会恢复事务开始前的数据
提交:commit;
提交后,事务结束,实现数据持久化
② 改变mysql的自动提交模式
MySql默认是自动提交的,也就是你提交一个DML语句,它就直接执行
set autocommit=0 禁止自动提交 set autocommit=1 开启自动提交
当使用 set autocommit=0 后,所有的SQL都将做为事务处理,直到你用commit确认或rollback结束
查看自动提交 : show global variables like ‘autocommit’
在事务开启后可执行任何其他操作,遇到commit才会写入至硬盘,遇到rollback会回滚到事务开启的那个时候
(2) 事务的使用
为了打开事务,允许在COMMIT和ROLLBACK之前多条语句被执行,我们需要做以下两步:
① 设置MySQL的autocommit属性为0,默认为1
② 使用 Begin或 start transaction语句显式的打开一个事务
备注:如果已经打开一个事务,则set autocommit=0不会起作用
因为START TRANSACTION会隐式的提交当前所有的更改,结束当前的事务,并打开一个新的事务
事务使用模板:
Begin; -- 开启事务
..... -- DML语句
.....
Commit; -- 提交事务
Except -- 如果出现异常
RollBack; --回滚数据
End; -- 结束事务
JDBC中使用事务
try{
connection.setAutoCommit(false); //不自动提交
//......执行jdbc代码
//sql1,sql2,sql3
connection.commit(); //提交事务
}catch(Exception e){
connection.roolback(); //事务回滚
}finally{
//清理资源
connection.setAutoCommit(true); //改回自动提交
}
3. 事务四大特征(ACID)
原子性 A:事务是业务的最小单位,同一个事务内部的一组操作必须全部执行成功(或者全部失败)
一致性 C:事务执行后的结果是和预设的规则完全符合的,不会因为出现系统意外等原因和预测的结果不一致,其它的三个属性都为了保证一致性而存在
隔离性 I:事务A和事务B之间不相关,即具有隔离性
持久性 D:是事务的保证,事务终结的标志(内存的数据持久到硬盘文件中)
4. 事务ACID特性的实现原理
在了解实现原理前,先要理解一些专业性词汇:redo log(重做日志)、undo log(回滚日志)、lock(锁)、MVCC(并发控制的方法)
Innodb架构的内存分布(只用看圈起来的部分)
(1) 原子性的实现
实现原子性特性的重点是 undo log(回滚日志),undo log用于记录数据被修改前的信息,存储反向的sql语句
以一个update语句事务回滚来说明redo log
begin;
update 修改内容 where id = 8; -- 这时BufferPool的内容已经被修改了
rollback;
① update语句执行,对应修改BufferPool中的页数据,使其变为脏页,交予Flush List控制
② update语句生成一个redo log对象,存储在 redo log buffer中
③ 生成 undo log (存储反向内容,相当于是update 原来的的数据 where id = 8
)
④ 执行rollback语句,通过执行对应undo log中的内容达到回滚
(2) 持久性的实现
实现持久性特性的重点是 redo log(重做日志)
redo log日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中
当事务提交之后会把所有修改信息都会存到该日志中
以一个update语句的事务提交执行流程来说明redo log
begin;
update 修改内容 where id = 8; -- 这时BufferPool的内容已经被修改了
commit;
① update语句执行,对应修改BufferPool中的页数据,使其变为脏页,交予Flush List控制
② update语句生成一个redo log对象,存储在 redo log buffer中
③ 生成 undo log
④ redo log 的内容持久化(事务commit的时候,如果事务rollback了则不会进行这一步)
⑤ bin log 持久化(binlog用于记录用户对数据库更新的SQL语句信息)
⑥ 返回客户端 修改成功
redo log 是如何做到数据恢复的?
假设在该事务执行的过程中,MySql挂掉了,当重启MySql后,读取磁盘中的该数据,那么这时读取的数据还是原始的状态
解释:
① redo log是存放在磁盘上的,所以磁盘上既有原来的数据,也有对应的redo log日志文件
② 当事务执行后,redo log的内容也会对应持久化在磁盘上
③ 当MySql重启后,假设再对该数据进行查询,MySql会同时提取磁盘中对应的 redo log 和 页数据到BufferPool中,并自动用redo log的内容对该数据页进行恢复
如果是事务进行了rollback,那么会根据 undo log的内容,反向操作数据,达到回滚的目的
如果不使用redo log的方式,就是直接到磁盘中去更新,但是磁盘是按页存取,而一页的数据很多,修改可能只是一小段数据,但是会对整页进行读写,效率也会比使用redo log低
补充:innodb提供了三种redo log持久化方式,可以设置
(3) 隔离级别及隔离性的实现
MySQL隔离级别有以下四种(级别由低到高):
① read uncommited(读未提交)
② read commited(读已提交)
③ repeatable read(可重复读)
④ serializable (串行化读)
级别越低的隔离级别可以执行越高的并发,但同时实现复杂度以及开销也越大
可靠性性高的,并发性能低
可靠性低的,并发性能高
以下语句可以设置MySql事务的隔离级别,默认是第3级别
SET session transaction isolation level read committed;
① read uncommited (读未提交 最低级别)
就是一个事务对BufferPool数据页的读写操作还未提交,但其他事务可以访问该数据页
事务中的修改即使还没提交,对其他事务是可见的
这时事务可以读取未提交的数据,但是会造成脏读(读取的是不稳定的数据)
优点:读写并行,性能高
缺点:造成脏读
无论是脏写还是脏读,都是因为一个事务去更新或者查询了另外一个还没提交的事务更新过的数据
但由于另外一个事务还没提交,所以他随时可能会回滚,那么必然导致你更新(查询)的数据就没了
② read commited (读已提交)
一个事务提交了之后,另外的事务可以去读取数据
InnoDB在 READ COMMITTED读取数据不加锁而是使用了MVCC机制,也就是采用了读写分离机制
该级别会产生不可重读以及幻读问题
不可重复读:同一个事务内多次读取同一行数据的结果不一样
出现不可重复读的问题和MVCC机制有关系
因为MVCC机制会在该隔离级别下每次 select的时候新生成一个版本号,也就是copy一个副本,所以每次select的时候读的不是同一个副本而是不同的副本
简单来说就是一个事务执行时,如果读取一个数据页多次
在中途的时候,如果这个数据页被其他事务改变了,但其他事务改变该数据后提交了,后续语句就可以读到,就造成了该问题
幻读:在同一事务内,后续读取读到之前没有的一批数据
例如:
① 事务 A 里有一个条件查询的语句 select name from t where id > 10,它进行了一次范围查询,查到了 10 行数据;
② 事务 B 往里面add了一批数据并提交
③ 事务 A 再次查询的时候,发现查到了 15 条,其中 5 条是之前没见过的。事务 A 以为出现幻觉了,为什么多出来了5条,这就是幻读
脏读与幻读的区别:脏读主要是因为另外的事务执行了update操作,而幻读是insert操作
③ repeatable read(可重复读 MySql的默认级别)
在这种级别下,在一个事务内的多次读取的结果是一样的
该级别可以避免,脏读,不可重复读等查询问题
mysql 有两种机制可以达到这种隔离级别的效果,分别是采用读写锁以及MVCC
方式一 读写锁:读锁+写锁 读锁可以共享
只要没释放读锁,在第二次读的时候还是可以读到第一次读的数据,从而解决不可重复读的问题
优点:实现起来简单
缺点:无法做到读写并行
方式二 MVCC:只加写锁,并且读取的是数据页的副本
为什么能可重复读?因为多次读取只生成一个版本,读到的自然是相同数据。
优点:读写并行
缺点:实现的复杂度高
锁机制
InnoDB 通过锁机制来保证事务间的隔离性
锁机制的基本原理可以概括为:
① 事务在修改数据之前,需要先获得相应的锁;
② 获得锁之后,事务便可以修改数据;
③ 该事务操作期间,这部分数据是锁定的,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。
MySQL锁的分类
① 按操作类型可以分为读锁(共享锁S)和写锁(排它锁X)
读锁:对同一份数据,多个读操作可以同时进行而不会互相影响
写锁:当前操作没有完成之前,会阻塞其他读锁和写锁
②按操作粒度分为行锁、表锁、页锁。
行锁指对某行数据加锁,是一种排它锁
表锁指对当前操作的整张表加锁,实现简单,资源消耗较少
页锁的锁定粒度介于行锁和表锁之间,一次锁定相邻的一组记录
补充:
表锁是对一整张表加锁,虽然可分为读锁和写锁,但毕竟是锁住整张表,会导致并发能力下降,一般不常用
行锁则是锁住数据行,这种加锁方法比较复杂,但是由于只锁住有限的数据,对于其它数据不加限制,所以并发能力强
MySQL一般都是用行锁来处理并发事务。这里主要讨论的也就是行锁
MVCC
MVCC称为多版本并发控制 ,读取数据时以类似快照的方式将数据保存下来,这样读锁和写锁就不冲突了,因为不同事务只能看到当前版本的数据,版本链的思想
MVVC只在RC(读已提交)和RR(可重复读)级别使用
InnoDB 实现 MVCC,多个版本的数据可以共存,主要是依靠数据的隐藏列(也可以称之为标记位)和undo log(回滚日志),隐藏列包括了该行数据的版本号、删除时间、指向undo log的指针等等
聚集索引有两个必要的隐藏列
trx_id : 用于记录对数据进行操作的事务的id
roll_point : 当有事务对数据进行修改时,会把老版本数据存储进undo log日志中,roll_point指针会指向该数据的位置,也就是老版本的位置,从而实现了版本链
当读取数据时,MySQL 可以通过隐藏列判断是否需要回滚并找到回滚需要的undo log,从而实现 MVCC;
这种读取方式就被称为Read View策略
上述的版本链和Read View策略合起来就形成了MVVC机制
read commited(读已提交)和repeatable read(可重复读)的区别就在于生成的Read View策略不一样
read commited(读已提交)的read view策略:
事务开启的时候就会创建read view,用于记录当前未提交的事务id,也就是活动中的事务,排序成一个事务id数组(可以称为read view数组)
当有事务需要操作数据时,就会获取该事务的id,对比read view数组中的事务id,通过比较知道该事务是否已提交,如果提交了则可以操作数据
例如read view中存储的事务id是[10,11,…,19,20],如果当前事务id是9,那么就小于read view中的id,说明不是活动事务,而且是已经提交的,说明可以访问数据(读已提交)
当然如果当前事务id>20,那么说明该事务在read view之后出现,在当前活动事务的后面,还未提交,不能访问数据
如果是事务10<id<20,那说明当前事务正是read view存储的活动事务,还未提交,也不能访问数据
不能访问数据的事务,会获取roll_point指针,去和上一个版本的数据对比,对比的即是上一个版本的事务id,再进行read view策略,从而知道能否读老数据
repeatable 可重复读的read view策略:
该级别下,事务每次查询都会生成一个新的read view数组,而RC级别是复用之前的read view数组
④ serializable (序列化读/串行化读)
使用了排他锁,也就是无论读写都会使用锁,不允许读读共享
优点:解决了所有问题
缺点:需要事务排队(使用排他锁)
隔离级别总结
(4) 一致性的实现
一致性是指:数据库总是从一个一致性的状态转移到另一个一致性的状态
就像ACID特性一开始总结的一样,一致性是其他三个共同完成的目标,在各种异常发生的时候,能做到恢复,能避免脏读、幻读、不可重复读等问题
所以一致性的实现即是其他三个属性共同的实现
ACID实现原理的总结:
原子性:使用 undo log(回滚日志) ,从而达到回滚
持久性:使用 redo log(重做日志),保障已提交事务的持久化特性,也能达到故障后恢复
隔离性:使用锁以及MVCC,运用的优化思想有读写分离,读读并行,读写并行,read view策略
一致性:利用前三个特性,通过回滚,以及恢复,和在并发环境下的隔离做到一致性
以上是关于MySQL事务实现原理的主要内容,如果未能解决你的问题,请参考以下文章