文章目录
更多相关内容可查看
事务基础
事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
事务特性
• 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
• 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
• 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
• 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
那实际上,我们研究事务的原理,就是研究MySQL的InnoDB引擎是如何保证事务的这四大特性的。
而对于这四大特性,实际上分为两个部分。 其中的原子性、一致性、持久化,实际上是由InnoDB中的两份日志来保证的,一份是redo log日志,一份是undo log日志。 而隔离性是通过数据库的锁,加上MVCC来保证的。
事务的隔离级别有哪些
脏读 | 不可重复读 | 幻读 | 概念 | 应用场景 | |
---|---|---|---|---|---|
read uncommitted | 有 | 有 | 有 | 事务可以读取其他事务尚未提交的数据修改 | 无 |
read committed | 无 | 有 | 有 | 事务只能读取已经提交的数据 | oracle、sql Server默认 |
repeatable read | 无 | 无 | 有 | 同一个事务内多次读取相同数据 | 金融系统、mysql默认 |
serializable | 无 | 无 | 无 | 事务之间的完全隔离,独立运行 | 电子商务订单处理系统 |
- 赃读:一个事务读到另外一个事务还没有提交的数据。
- 不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读。
- 幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了 “幻影”。
事务隔离级别的配置方式
建立数据库连接时
Connection connection = // 获取数据库连接
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
持久层框架hibernate中 可以通过注解(@Transaction)或者配置文件
@Transaction(isolation = Isolation.READ_COMMITTED)
public void myTransaction() {
// 事务代码
}
Spring框架,事务管理器中配置
@Transactional(isolation = Isolation.READ_COMMITTED)
public void myTransaction() {
// 事务代码
}
使用数据库特定的语句来实现
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
事务隔离级别的实现方式
隔离级别的实现主要有读写锁和MVCC( Multi-Version Concurrency Control )多版本并发处理方式。
- 读未提交,它是性能最好,也可以说它是最野蛮的方式,因为它压根儿就不加锁,所以根本谈不上什么隔离效果,可以理解为没有隔离。
- 串行化,读的时候加共享锁,也就是其他事务可以并发读,但是不能写。写的时候加排它锁,其他事务不能并发写也不能并发读。
- 读已提交在读已提交隔离级别下,在事务中每一次执行快照读时生成ReadView。多个事物有多个ReadView , 事务没有结束之前只能读取自己的ReadView , 实现了读已提交 , 不能读取到其他事物未提交的数据
- 可重复读在可重复读隔离级别下,仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。 而Repeatable Read 是可重复读,在一个事务中,执行两次相同的select语句,查询到的结果是一样的
什么是redolog
, 有什么用 ?
redolog就是重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性
该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中, 用于在刷新脏页到磁盘,发生错误时, 进行数据恢复使用
在InnoDB引擎中的内存结构中,主要的内存区域就是缓冲池,在缓冲池中缓存了很多的数据页。
1、当我们在一个事务中,执行多个增删改的操作时,InnoDB引擎会先操作缓冲池中的数据,如果缓冲区没有对应的数据,会通过后台线程将磁盘中的数据加载出来,存放在缓冲区中,然后将缓冲池中的数据修改,修改后的数据页我们称为脏页。
2、而脏页则会在一定的时机,通过后台线程刷新到磁盘中,从而保证缓冲区与磁盘的数据一致。 而缓冲区的脏页数据并不是实时刷新的,而是一段时间之后将缓冲区的数据刷新到磁盘中,假如刷新到磁盘的过程出错了,而提示给用户事务提交成功,而数据却没有持久化下来,这就出现问题了,没有保证事务的持久性。
redolog的存在就是为了解决这个问题 当对缓冲区的数据进行增删改之后,会首先将操作的数据页的变化,记录在redo log buffer中。在事务提交时,会将redo log buffer中的数据刷新到redo log磁盘文件中。 过一段时间之后,如果刷新缓冲区的脏页到磁盘时,发生错误,此时就可以借助于redo log进行数据恢复,这样就保证了事务的持久性。 而如果脏页成功刷新到磁盘 或 或者涉及到的数据已经落盘,此时redolog就没有作用了,就可以删除了,所以存在的两个redolog文件是循环写的
问题 : 为什么每一次提交事务,要刷新redo log 到磁盘中呢,而不直接将buffer pool中的脏页刷新到磁盘呢 ?
因为在业务操作中,我们操作数据一般都是随机读写磁盘的,而不是顺序读写磁盘。 而redo log在往磁盘文件中写入数据,由于是日志文件,所以都是顺序写的。顺序写的效率,要远大于随机写
什么是undo log
, 有什么用 ?
Undo Log在MySQL的InnoDB存储引擎中扮演着重要的角色,特别是在事务处理、回滚操作以及多版本并发控制(MVCC)方面。以下是Undo Log的实现原理:
目的:
Undo Log的主要目的是在事务发生错误时提供回滚(Rollback)的能力,以及在多版本并发控制(MVCC)下支持非锁定读操作。
记录内容:
在事务开始之前,InnoDB会将要修改的数据的原始版本写入Undo Log。这包括修改前的数据值以及相关的元数据,如数据页的位置、修改的行等。
事务处理:
- 当事务进行时,InnoDB首先会生成一个Undo Log记录,其中包含修改前的数据状态
- 如果事务成功提交,那么Undo Log通常会被保留一段时间,以支持MVCC的读操作,但随后可能会被清理(取决于配置和存储引擎的策略)。
- 如果事务失败或需要回滚,InnoDB会使用Undo Log中的信息来撤销事务对数据的修改,将数据恢复到事务开始前的状态。
MVCC支持:
Undo Log是InnoDB实现MVCC的关键组件。MVCC允许事务在读取数据时看到一个一致的数据快照,即使其他事务正在并发地修改这些数据。通过Undo Log,InnoDB可以为每个事务提供一个数据快照,使得读操作不需要等待写操作的完成。
存储和管理:
Undo Log是循环写入的,这意味着当Undo Log空间用完时,它会覆盖最旧的日志记录(这取决于配置和存储引擎的策略)。因此,长时间运行的事务可能会导致Undo Log空间不足,需要DBA进行监控和管理。
与Redo Log的关系:
Redo Log和Undo Log在InnoDB中都是非常重要的日志类型,但它们的职责不同。Redo Log主要关注于确保事务的持久性,即在数据库崩溃后能够恢复未提交到磁盘的数据。而Undo Log则关注于提供事务的回滚能力和支持MVCC。
什么是binlog
, 有什么用 ?
binlog是一个二进制格式的文件,用于记录用户对数据库更新的SQL语句信息默认情况下,binlog日志是二进制格式的,不能使用查看文本工具的命令(比如,cat,vi等)查看,可以使用mysqlbinlog解析查看
记录内容:
Binlog主要记录了对数据库执行更改的所有数据或表结构变更语句的信息,包括所有DDL(数据定义语言)和DML(数据操纵语言)语句,但不包括SELECT或SHOW等查询语句。Binlog记录的是事件(events),而不是SQL语句本身。这些事件描述了数据库在某个时间点上的状态变化
格式:
- MySQL支持三种不同的Binlog格式:STATEMENT(基于SQL语句)、ROW(基于行的更改)和MIXED(混合模式)。
- STATEMENT格式记录的是执行的SQL语句本身,但在某些情况下可能会产生不一致的结果(如非确定性函数)。
- ROW格式记录的是行的更改,它记录了每一行数据的更改前和更改后的值,因此更为可靠。 MIXED格式则是根据语句类型自动选择STATEMENT或ROW格式。
写入方式:
- Binlog的写入是在事务提交后进行的,因此它记录的是已经完成的事务操作。
- Binlog采用的是追加写入的方式,每个Binlog文件都会不断增长,直到达到指定的大小限制或达到时间限制。
- 当达到限制后,MySQL会自动切换到下一个Binlog文件,并预分配下一个Binlog文件的空间。
作用:
数据复制
:Binlog被用于数据库的主从复制。主数据库将写操作记录到Binlog中,从数据库通过读取并执行Binlog中的操作,实现与主数据库的数据同步。数据恢复
:在数据丢失或误操作的情况下,可以通过重放Binlog来恢复数据到某个时间点。
GTID(全局事务标识符):
在MySQL的主从复制中,GTID用于唯一标识一个事务。当主服务器更新数据时,会在事务前产生GTID,并一同记录到Binlog日志中。从服务器在读取并应用Binlog时,会检查GTID,以确保不会重复应用相同的事务。
Relay Log:
在主从复制中,从服务器在接收到主服务器的Binlog后,会将其写入到本地的Relay Log中。随后,从服务器的SQL线程会从Relay Log中读取事件并应用到从数据库上。
优化:
可以通过调整innodb_log_file_size等参数来优化Binlog文件的预分配机制,以减少磁盘I/O的开销和浪费的磁盘空间。
所以他的主要作用就是是用于数据库的主从复制及数据的增量恢复
MVCC原理
MVCC也叫多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突
通俗的讲就是MVCC通过保存数据的历史版本,根据比较数据的版本号来决定数据的是否显示,在不需要加读锁的情况就能达到事务的隔离效果,最终可以在读取数据的时候可以同时进行修改,修改数据时候可以同时读取,极大的提升了事务的并发性能,假如我们创建一张表 :
们创建了上面的这张表,我们在查看表结构的时候,就可以显式的看到这三个字段。 实际上除了这三个字段以外,InnoDB还会自动的给我们添加三个隐藏字段及其含义分别是
- DB_TRX_ID : 最近修改事务ID , 记录插入这条记录或最后一次修改该记录的事务ID
- DB_ROLL_PTR : 回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本
- DB_ROW_ID : 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段
当有多个事务同时对这个表的数据进行修改 , 这个时候就会形成版本链
事务执行SQL语句时,产生一个读视图。也就是
Read View
, 实际上在innodb中,每个SQL语句执行前都会得到一个Read View , Read View的主要作用是来做可见性判断的,即判断当前事务可见哪个版本的数据 , Read View 主要包含几个字段 :
m_ids
: 当前活跃的事务ID集合min_trx_id
: 最小活跃事务IDmax_trx_id
: 预分配事务ID,当前最大事务ID+1(因为事务ID是自增的)creator_trx_id
: ReadView创建者的事务ID
在readview中就规定了版本链数据的访问规则 , trx_id 代表当前事物id:
trx_id == creator_trx_id
: 数据是当前这个事务更改的 , 可以访问trx_id < min_trx_id
: 说明数据已经提交了 , 可以访问数据trx_id > max_trx_id
: 说明该事务是在ReadView生成后才开启 , 不可以访问数据min_trx_id <= trx_id <= max_trx_id
: 如果trx_id不在m_ids中,是可以访问该版本的
基于MVCC查询一条记录,执行流程如下 :
- 获取事务自己的版本号,即事务ID
- 获取Read View
- 查询得到的数据,然后Read View中的事务版本号进行比较。
- 如果不符合Read View的可见性规则, 即就需要Undo log中历史快照;
- 最后返回符合规则的数据
不同的隔离级别,生成ReadView的时机不同:
- READ COMMITTED :在事务中每一次执行快照读时生成ReadView。
- REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。
- 读操作的版本控制: 当执行读操作时,数据库系统会根据事务开始时的时间戳或者事务ID来确定可见的数据版本。通常情况下,读操作只能看到在该事务开始之前提交的数据版本,而不能看到后续提交的数据版本。
- 写操作的版本管理: 当执行写操作时,数据库系统会创建一个新的数据版本,并将新版本的数据写入数据库中。同时,数据库系统会更新事务开始之前的旧版本数据的版本号,使得旧版本数据对于之后的读操作不可见。
- 数据的回收和清理: 当事务提交后,旧版本数据可能会成为不可见的。数据库系统会定期清理这些不可用的旧版本数据,以释放存储空间。
本篇小结
MVCC多版本并发控制,意思就是事物在进行数据的写的操作的时候不会对当前数据进行修改,而是创建一条新版本的一条数据,新版本的这条数据会有自己的事物id,版本号,然后在进行读操作的时候,数据库其实会有三个隐藏字段,最近修改事物的id,回滚指针,隐藏主键,在事物执行sql语句之前会的到一个ReadView,然后用当前事务的id去跟ReadView中的字段进行对比,然后去判断是否可以读到这一条数据,如果多个事物同时访问同一个数据的时候,就会产生一种版本链的一种结构,如果不符合ReadView可见性规则,就需要读Undo log中历史快照,最后返回符合规则的数据,然后数据库会更新事物开始之前的旧版本数据的版本号,让旧版本的数据对之后的读操作不可见
有小伙伴对其他数据库内容感兴趣,可以通过以下链接进行查看
数据库-索引(基础篇)
数据库-索引结构(B-Tree,B+Tree,Hash,二叉树)
数据库-索引语法(增删查)
数据库-索引分类(主键索引、唯一索引、普通索引、全文索引)
数据库-索引使用(验证索引效率、单列索引与联合索引、最左前缀法则)
索引失效情况
数据库-Mysql锁详解(全局锁、表级锁、行级锁)