MYSQL事务
事务是一组操作单元,它将数据从一种状态转变到另外一种转台。
特性(ACID)
原子性(Atomicity)
原子性描述的事务中的操作时一组不可分割的单元,要么全部成功要么全部失败。
保证原子性的日志undo log
事务在执行过程当中可能会出现语句错误,服务器错误,甚至服务器断电等错误。或者程序执行过程中回滚。这样在事务中执行的操作都应该撤销是的数据回归到事务没开始执行到状态,这个歌过程叫做回滚,undo日志时一个用于回滚的逻辑日志,比如说我们执行插入数据的操作鴤会记录当前插入的主键,当需要回滚时执行一个delete操作。当我们执行一个删除操作时undo需要记录下这条记录的所有值当回滚时重新插入这一条数据,当我们执行update操作时需要记录下修改操作之前的值当执行回滚操作时反向修改数据的值。这样以保证事务的原子性使得事务要么处于处于回滚状态要么处于提交状态。
undo日志的存储结构和执行过程
undo log内部使用的存储结构时rollback segment,roback segment中又细分为一个个的undo log segment,并且一个rollback segment中最多只能存在1024个undo log segment,在MySQl1.1之前只一个rollback segment,从1.2版本开始可以有128个rollback segment。
①rollback segment中数据的分类
主要分为未提交的undo页,已经提交但是未过期的undo页,已经提交已经过去的鴤页
②rollback segment于事务:
一个事务对应这一个rollback段,但是一个rollback段可以对应多个事务,在事务执行的过程当中事务即将不断填充回滚段,当空间被用完的时候事务会选择服用已经过期的事务所使用空间或者在rollback segement所也允许的范围内开辟性的空间。回滚段存在undo表空间中,数据库中可以有多个undo表空间但是同一时刻只能使用一个undo表空间
底层存储
undo log内部使用的存储结构时rollback segment,roback segment中又细分为一个个的undo log segment,并且一个rollback segment中最多只能存在1024个undo log segment,在MySQl1.1之前只一个rollback segment,从1.2版本开始可以有128个rollback segment。
①rollback segment中数据的分类
主要分为未提交的undo页,已经提交但是未过期的undo页,已经提交已经过去的鴤页
②rollback segment于事务:
一个事务对应这一个rollback段,但是一个rollback段可以对应多个事务,在事务执行的过程当中事务即将不断填充回滚段,当空间被用完的时候事务会选择服用已经过期的事务所使用空间或者在rollback segement所也允许的范围内开辟性的空间。回滚段存在undo表空间中,数据库中可以有多个undo表空间但是同一时刻只能使用一个undo表空间
执行过程
undo log是一个逻辑日志,所以它的改变时逻辑上的改变即原先执行了一个什么操作我们只需要执行一个该操作的逆操作使得数据回滚到最远先的状态即可:
①例如原先执行了一个插入操作,我们记录下插入操作主键,等到回滚的时候通过主键执行一个delete操作
②比如说执行了一个删除操作,我们记录下数据的整条内容,等到回滚的时候我们将这个数据完整插入会表中。
③载比如说执行了一个update操作我们记录下修改操作之前的内容,等到回滚的时候我们执行一个反的update操作
undo日志的作用
undo log的作用主要有两个:
①回滚:将事务回滚到事务开始之前的状态
②MVCC:多版本并发控制,在不同的隔离级别中我们通过版本号来决定当前事务到底访问的是哪个版本的数据
undo日志的生命周期
undo log的生命周期分为生成,回滚和删除三个过程:
①生成:一个事务的执行流程大概上是这样,首先查看数据库缓冲池中是否存在当前记录的页,如果并不存在先去磁盘加载当前的数据页,然后更具Write Ahead Logging的原则首先写入到undo log,在缓冲池中修改数据记录,记录redo日志,刷盘,记录到bin
log
②回滚:事务可能一因为发生错误而胡滚也可能有程序员主动发起回滚
③删除:这里需要分类insert undo log当数据提交的时候已经过期,update redo log 需要mvcc不再使用到这一条记录时才过期
日志记录的分类
大体上可以分为两类:
①插入的回滚日志:
插入的回滚日志在事务在事务提交之后就可以认定卫视过期的undo日志了。
②修改额回滚日志:
修改的undo日志在事务提交之后由于还需要提供mvcc使用所以不能够立即认定其国企。
insert undo log
upadate undo log
一致性(Consistent)
描述的是数据从一个合法性状态转移到另一个合法性状态
隔离性(Isolation)
一个事务执行的过程不能被其他事务所干扰,其他事务不能够干扰本事务的运行,各个事务之间相互不干扰
事务并发可能会引起的几个问题
当多个事务并发执行的时候,就像多个线程并发执行总会引起一些问题,浙西问题可能会造成数据库中的数据错误。
脏写
两个事务共同访问一个数据,一个事务过的回滚或者提交覆盖了另一个对事务的修改。
脏读
两个事务共同操作一份数据一个事务能够读到另一个事务没有提交的修改,这个时候读到的数据称之为脏数据,这种现象称之为脏读。
不可重复读
一个事务执行过程当中中间由于其他事务对数据的修改该前后两次读取到的数据不一致。
幻读
一个事务两次读取多个数据行时,后一次读取到前一次没有读取到的数据。
隔离级别
事务的四大隔离级别
数据库中定义了四个隔离级别Oracle只支持读已提交和串行化两个隔离级别默认支持的时读已提交。MySQL则支持四个隔离级别,隔离级别越高解决的问题越多,解决这些问题付出的代价也就越多性能越差,所以MySQL默认的隔离级别时Repeatable Read
读未提交
读未提交解决了脏写问题
读已提交
读已提交解决了脏写,脏读问题
可重复读
可重复读解决了脏写,脏读,不可重复读问题
串行化的
串行化解决了脏写,脏读,不可重复读和幻读等所有的问题
设置隔离界别的方式
设置隔离级别有两种方式:
方式一:set transaction isolation level repeatable read
方式二:set sesseion/global transaction isolation = “repeatable-read”
实现隔离性的机制
Mysql事务读写中的问题:
①读读不会引起并发问题,
②读写和写读会导致脏读,不可重复读和幻读等问题,解决这些问题的方法有两种一种是使用乐观锁机制解决问题一种是使用悲观锁机制解决问题
乐观锁:
乐观锁解决读写和写读问题的方案就是在读时使用mvcc机制,写时加锁。涉及到的隔离级别就是读已提交和可重复读
悲观锁:
悲观锁机制解决问题就是读写加锁,涉及到的隔离级别就是串行化
MVCC(MultiVersionConcurrentControl多版本并发控制)
mvcc翻译过来叫做多版本并发控制,多版本说的是使用undo日志中一行数据的不同版本,访问每一行记录的时候使用回滚指针指向undo日志以方便访问undon日志。并发控制主要指的是readview。
mvcc机制在不同隔离级别下的实现不同
首先明确一个点,mvcc只存在于读已提交和可重复读这两个隔离级别中所以我们讨论mvcc在不同的隔离界别下的不同就说的时这两个隔离界别。在read_commited这个隔离级别下每当读取一次数据就获取新的readview这样每次读取到的数据版本就是最新的所以容易引起不可重复读和幻读的问题。而在可重复读这个隔离界别下readview指在第一次读取数据的时候产生后续所有的的读操作都公用这个readview这样就会解决幻读和不可重复读的问题。
mvcc机制的实现
####### mvcc三剑客
mvcc的实现主要靠的mvcc三剑客,分别是undo日志,readview和列的隐藏字段
######## 列的隐藏字段
在innodb的行格式中有这么三个隐藏列
它们分别是row_id,tx_id和回滚指针。这三者在mvcc中有用的是tx_id和回滚指针,tx_id用于和readview中的版本号对比匹配看这条记录是否看课件,回滚指针用于指向下一条记录当当前的几里路不可用的时候跳到下一条记录接着比较知道找到可用的记录为止。
######## undo日志
######## readview
readview说白了就是一个当前活跃在系统中的事务的快照使用一个数组来表示
######### readview中的结构
readview中包括的东西很简单,创建当前read的事务的事务id,系统中活跃的事务数组,这边特殊需要注意的是up_limit_id和low_limit_id
########## up_limit_id
记录的是当前活跃事务id中最小的
########## low_limit_id
记录的是已经使用的最大事务号+1
####### 三剑客是如何配合的
①访问一条数据时首先取出它的事务id,和readview中的创建事务id作比较如果相同直接访问
②第一步如果不同,比较up_limit_id如果记录的事务id比它小直接访问
③如果上一步失败了直接比较low_limit_id如果记录的事务id比这个值大1则说明这个视图形成的时候修改这条记录的事务还没开始运行所以直接宣告不能访问,跳转到下一条记录
④如果记录id比上一步的值要小那么,说明记录事务id在当前活跃事务中,就需要查看这个id现在是否还在活跃,如果还在活跃就表示不能能狗读取,如果不在活跃说明事务已经提交可以读取。
锁机制
在mysql中的锁和java高并发中的锁其实是一个锁。都是为了让各个事务(线程)能够按个有序的执行不要引起线程不安全或者说数据不安全的我问题
锁的分类
####### 不同存储引擎下的锁
锁是在存储殷勤曾实现的对于不同的存储引擎来说锁的实现是不一样的innodb为了处理高并发的场景必须要设置行锁,页锁,表锁三种不同粒度的锁来区分不同并发场景下的使用。对于不支持事务的MyISAM存储殷勤来说只需要支持表锁就足够了
####### 锁的具体分类
######## 按照锁的粒度分类
对于innodb来说必须要设置不同的锁粒度区分不同的应用场景。行锁,页锁和表锁粒度越来越大并发性越来越差对应的来说付出的开销也越来越小。每个级别的锁的个数其实都是有限制的当一个级别的锁达到系统能够分配的锁的最大限度的时候会发生锁升级。
######### 表锁
表锁是所有的锁当中粒度最大的锁,对于InnoDB和MYIIAM来说实现表锁的机制几乎一模一样。但是对于InnoDB来说它有更多的选择所以在考虑并发性上来说使用到表锁的频率并不是很高
########## 普通的X锁和S锁
表级别的S锁和X锁共享锁和排他1锁
########### 如何加锁
lock table t READ
lock table t WRITE
########### 锁的兼容性
如果给表上了一个S锁那么本事务可以对表中的数据读操作不能写操作,其他事务能读不能写,本事务自己也不能操作别的表。如果给表上了一个X锁本事务能够读写,其他事务其他事务不能够读写,本事务也不能操作其他表。
########## 意向锁
意向锁分为IS和IX锁其实意向锁的出现就是为了改变提高表锁和行锁协同作用时候的一个性能。如果不存在意向锁在给表加表锁的时候需要逐个扫描表中的每一行数据是否加锁这很耗费时间也很难以实现。所以当一个事务给本表中的记录加锁的时候也会自动给本表加上意向锁,以方便其他事务给本表加上表级别的锁的时候快速判断是否加锁。
########## 元数据锁
元数据锁其实就是在修改表的结构的时候给表加锁,不能够再一个事务修改表的结构的时候其他事务来访问本表。
########## 自增主键锁
########### 数据插入的三种方式
①简单插入
使用普通的insert语句插入
②批量插入
使用insert加上select插入
③混合插入
前两中模式混合插入
########### 自增主键锁实现的三种方式
######### 页锁
页锁其实就是基于数据页来加锁,其性能和并发度上来说都基于表锁和行锁之间。
######### 行锁
########## REcord Lock
Record Lock很好解释,就是对一行的数据加锁。
X:select … for update
S:select … for share
select … lock in share mode
########## Gap Lock
这个锁其实就是我们所说的间隙锁,它锁住的其实就是iyge没有插入数据的一个范围,如果要对数据加上一个间隙锁也很简单就是对区间内的任何一条数据加上锁,其实加上S和X锁的区别不大本质上都是加上一个间隙锁
########## Next-Key Lock
Next—key Lock可以说是Record Lock和Gap Lock的一个组合体他所著的是一个半开半闭区间,再Repeatable Read这个隔离级别下锁使用的就是临键锁
########## 插入意向锁
插入意向锁页很好解释当我要插入数据的位置被加了一个间隙锁的时候我们无法插入数据所以只能够等待,并且这个等待的事务是由插入意向的所以称之为插入意向锁
######## 按照锁的加载方式分类
按照锁的加载方式来分类的话锁可以分为隐式锁显示锁,其实就是手动加锁和被动加锁
######### 隐式锁
隐式锁的分类页十分简单,就是插入意向锁和插入锁。
########## 插入意向锁
########## 插入锁
对于一个事务来说再本事务内新插入的及记录再本事务结束之前是不能够被其他的事务读写的。所以这种记录再被其他事务访问的时候会有一个隐式加锁,如果其他事务要访问本记录如果是通过聚簇索引来访问的那么就会通过记录中的tx_id这个隐藏列来判定的插入这条事务是否还在活跃如果是通过二级索引来访问的那莪也会通过二级索引中的页结构中的一个事务id字段来判断当前事务是否活跃。如果是活跃的访问的事务会帮助本事务产生一个所结构,当本事务执行完毕之后唤醒给等待事务解锁。
######### 显示锁
######## 锁的实际使用上分类
根据实际随用上来分类锁可以分为共享锁和排他锁
######## 按照对待并发问题的态度分类
根据对待锁的态度上来分别可以分为悲观锁和乐观锁,这两者其实都是锁的涉及思想。
悲观锁:悲观锁其实就是考虑本数据总是会和其他事务产生并发问题,所以使用加锁的手段使得事务一个挨一个执行。这种涉及的思想用在写比较多的场景之下。
乐观锁:对数据安全问题采取乐观的态度,其他事务不会和本事务产生并发问题,大多数采用版本号来实现最经典的实现就是使用mvcc和CAS机制这种适合用在读比较多的场景之下
######## 全局锁和死锁
######### 全局锁
全局锁其实就是在数据库的层面加了一个锁,其他对本数据库操作的事务只能够读取数据不能够修改数据库中的数据
######### 死锁
锁的结构
####### 什么情况下可以共用一把锁
在InnoDB中只有同属于一个事务操作的是同一个数据也锁的状态和锁的类型是一致的才能公用一个锁结构
####### 锁的具体结构是什么
######## 表锁结构
表锁的结构包括锁归属的事务,锁的表信息,和lock_mode
######## 行锁结构
行锁的基本信息包括行锁归属的是哪个事务,使用的哪个索引这两个值都是一个指针。接着描述行锁的lock_mode,n_bit
######### lock_mode
lock_mode包含三个方面的信息,第一个是锁的类型是S,X,IX,IS还是INC_LOck。第二个描述的是一个锁是行锁还是表锁。第三个描述的是如果是一个行锁描述行锁的具体类型和is_waiting
######### n_bit
这里头记录的是当前的行锁锁定的是哪个表空间下的哪个页,并且页中的一条记录使用一个bit表示
锁的监控
####### 常用的锁监控工具
show status like “InnoDb_row_locks”
####### 数据库系统表中的锁监控工具
在整个锁的监控当中涉及到的数据库包括
information_schema:这里头只包含一张表InnoDB_TRX
另一个数据库则记录这锁的信息,perfoemance_schema下面有data_locks和data_lock_wait两张biao
持久性(Durability)
事务的操作会被持久化刷盘到磁盘上永久保存。
保证持久性的日志redo log
一个事务的说有修改该操作当事务提交时其实只是在数据库缓冲池中做了一个修改,如果我们要将这些修改刷盘,则需要一个日志保证数据不丢失。这个日志就是redo日志它是一个用于保证刷盘操作成功也就是用于保证事务的持久性的一个物理日志。
redolog组成与执行过程
组成
redolog总体上分为redo log buffer(缓冲区)和redo log file(文件),这其实是必要的首先把redo的信息记录在缓冲区中这样提高记录的整体效率,然后再将缓冲区刷盘。
####### redo log buffer
redo log的缓冲区其实就是内存中的一块内存空间。这块缓冲区的大小通过innodbb_log_buffer_size参数来设置默认为16M最小为1M
######## 组成
redo log buffer有一个个的redo log block组成。
######## 写入过程
要明白redo log buffer的写入过程首先得引入MIni Transaction的概念,它是对数据页一条记录的修改操作,一个事务可以拆分成多条sql语句一条sql语句又可能对多个数据页中的多条记录起作用,所以一条sql包括一个或者等多个Mini Transaction.与Mini Transaction对应的就是一个个的Redo log Bolck,在实际的数据库事务运行场景当中可能有多个事务同时进行,redo log buffer记录的原则就是必须一每一个Mini Transaction为原子单位可以交替的记录。
####### redolog file
redo log file是真正在磁盘上存储redo log的地方它分为一个个的文件每个文件的大小默认为48M,最大的存储大小为512G,并且将这些文件分为一个个的redo log gruop
######## 几个重要参数
①innodb_log_file_size:
②innodb_log_files_in_group
③innodb_log_file_home_dir
######## 重用机制
redo log的存储以一个组为单位,一个组围成一个环,并且定义了两个指针checkpoint(已经可以重用的位置),write_pos现在使用到什么位置。
执行过程
####### 执行的总体过程
一个事务的执行过程是这样的,一个事务被拆分为一条条的sql语句。每条sql都是这也执行的,首先看看数据库缓冲池当中是否存在我们要修改的页,如果没有就加载,根据Write Ahead loggin的原则先记录到undo log当中原先的数据,更新缓冲区中的数据,开始记录redo log,redo log刷盘,将记录刷盘,记录bin log。
####### 刷盘策略
刷盘策略主要指的是redo log buffer同步到磁盘的过程,由innodb_flush_log_at_tx_commit参数指定,首先注意刷盘涉及都三个区域,首先是redo log buffer,其次是page cache(操作系统的一块缓冲区),最后才是redo_log_file
三个值:
①值为0,默认不管刷盘操作等着merge线程每一秒buffer中的信息一下子同步到磁盘。这个刷盘的过程从redo log buffer直接刷到redo log file
②值为1,这个值代表每个操作都直接同步到磁盘。
②值为2,每次操作都从innodb_log_buffer同步到page_cache,而不管page_cache到redo_log_file
这三者的区别在于第二种最为安全每一次操作都更新,第一种操作性能最好但是不能保证安全性。第三种操作取中间状态,MySQL挂了没关系但是操作系统挂了数据库任然diu’shi
优点
①log文件占据的空间小
②可以减少mysql缓冲池中数据的刷新频率
特点
①每个操作都会不断写入到redo log
②在磁盘上的写入是顺序IO提高效率
事务的状态
事务的执行过程当中一共有五种状态分别为:执行,半提交,提交,失败,回滚。
①执行:指的是事务正在执行操作中的一条语句或者正从一条语句转向下一条语句
②半提交:指的是事务所有语句已经执行完毕并且所有的修改操作都已经修改到数据库缓冲区当中,但是没有刷盘。
③事务已经完成所有的操作并且刷盘成功
④失败:事务在执行过程当中因为语句错误,违反约束,数据库宕机等原因导致事务执行失败。
⑤回滚:事务在对执行过的所有操作撤销,可以从执行或者失败状态转移到回滚状态。
事务的分类
事
按照事务是否自动提交
事务按照是都自动提交分为显式事务和隐式事务两种,这两者的区别在于执行完一条语句之后是否自动commit
显示事务
显示的事务在执行事务时需要我们主动开启事务,事务执行过程当中可以设置保存点,事务执行完毕之后需要我们手动提交事务。
①开启事务
可以使用begin和start transaction开启事务这两者的区别在于begin只是简单开启了一个事务,而使用后者可以指定这个事务是只读的还是读写的,是否开启一致性读
eg:start transaction read only/read write [with consistent snapshot]
②事务过程当中使用保存点
设置保存点:savepoint savepointName
回滚到保存点:rollback to savepointName
删除保存点:release savepointName
③事务执行结束调用rollback或者commit
隐式事务
隐式的事务就是每当我们执行对数据库权限设置,数据定义语言,或者DML的时候没执行一条语句就自动提交。
关闭自动提交的方法:
①设置autocommit参数关闭自动提交
②使用开启显示事务的方法开启一个事务
按照事务的复杂程度
按照事务的复杂度分类就事务可以分为扁平事务(简单事务),带保存点的扁平事务,链式事务,嵌套事务和分布式事务
扁平事务
就是最简单的事务
带保存点的扁平事务
在事务中设置保存点,当事务很长的时候如果执行错了一个操作可以选择回滚到上一个保存点,这样可以重新从保存点开始往下做