事务
mysql分层
mysql逻辑架构可以分为三层:
客户端层:处理连接和认证授权
服务层:解析、优化sql,查询缓存以及内置函数的实现
存储引擎:负责mysql的存储和提取,事务就是由存储引擎层实现的
事务
事务是访问和更新数据库的执行单元,可能包含一个或多个sql语句
事务其实可以分为两种,隐式的和显式的,mysql默认是开启autocommit的,即平时的增删改查操作都是隐式的事物操作,显示的事务则是指手动的开始、执行sql和提交的过程,如何写一个显示的事务:
首先关闭自动提交功能:
set autocommit = 0;
然后开启一个事务:
start transaction;
…开始执行sql…
最后再提交/回滚
commit/rollback
特殊操作:DDL语句执行完是默认强制提交事务
事务四大特性
A原子性:事务是不可分割的,要么全部执行/不执行,即要么成功要么回退(commit/rollback)
C一致性:事务必须使得数据库从一个状态到另外一个一致性状态,即事务不能破坏数据库的完整性和约束(事务追求的终极目标)
I隔离性:一个事务的执行不能被其他事务所干扰
D持久性:一个事务操作提交成功后,对数据库的改变应当是永久的
原子性实现原理
undo log 回滚日志
MySQL的日志有很多种,如二进制日志、错误日志、查询日志、慢查询日志等,此外InnoDB存储引擎还提供了两种事务日志:redo log(重做日志)和undo log(回滚日志)。
其中redo log用于保证事务持久性;undo log则是事务原子性和隔离性实现的基础。
下面说回undo log。实现原子性的关键是:简单来说修改数据库时的操作会放到undo日志中,如果事务执行失败或回滚时,就可以利用undo日志把数据恢复到之前的样子
undo log属于逻辑日志,它记录的是sql执行相关的信息。当发生回滚时,InnoDB会根据undo log的内容做与之前相反的工作:对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相反的update,把数据改回去。
以update操作为例:当事务执行update时,其生成的undo log中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到update之前的状态。
隔离性实现原理
隔离性要求同一时间只能有一个事务修改数据库,InnoDB通过锁机制来保证这一点:
简单来说就是一个事务修改数据之前必须获得相应到锁,只有有锁才能修改数据,在此期间其他事务想修改这部分内容必须等待当前事务提交或回滚后释放锁。
然后mysql中的可分为行级锁,表级锁以及介于二者之间的锁
表级锁在操作数据时会锁定整张表,并发性能较差;
行锁则只锁定需要操作的数据,并发性能好。但是由于加锁本身需要消耗资源(获得锁、检查锁、释放锁等都需要消耗资源),因此在锁定数据较多情况下使用表锁可以节省大量资源。
MySQL中不同的存储引擎支持的锁是不一样的,例如MyIsam只支持表锁,而InnoDB同时支持表锁和行锁,且出于性能考虑,绝大多数情况下使用的都是行锁。
并发性能好的消耗大,所以采用哪种锁也是一种折衷权衡
查看数据库中的锁:
select * from information_schema.innodb_locks; #锁的概况
show engine innodb status; #InnoDB整体状态,其中包括锁的情况
持久性实现原理
redo log 重做日志
InnoDB作为MySQL的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘IO,效率会很低
简单来说,mysql为了提高数据读取效率,使用了缓存(Buffer Pool),读取数据时先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;而写入数据时也是先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。
这样就带来了一个问题,就是宕机时缓存中的数据会丢失,怎么办呢,就引入了redo log:当数据修改时,不仅写入缓存,还会在redo log中记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘,如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。
既然redo log也需要在事务提交时将日志写入磁盘,为什么它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快呢?主要有以下两方面的原因:
(1)刷脏是随机IO,因为每次修改的数据位置随机,但写redo log是追加操作,属于顺序IO。
(2)刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效IO大大减少。
redo log与binlog
我们知道,在MySQL中还存在binlog(二进制日志)也可以记录写操作并用于数据的恢复,但二者是有着根本的不同的:
(1)作用不同:redo log是用于宕机后恢复的,保证MySQL宕机也不会影响持久性;binlog则是用于保证服务器可以基于时间点恢复数据,此外binlog还可用于主从复制。
(2)层次不同:redo log是InnoDB存储引擎实现的,而binlog是MySQL的服务器层实现的,同时支持InnoDB和其他存储引擎。
(3)内容不同:redo log是物理日志,内容基于磁盘的Page;binlog的内容是二进制的,根据binlog_format参数的不同,可能基于sql语句、基于数据本身或者二者的混合。
(4)写入时机不同:binlog在事务提交时写入;redo log的写入时机相对多元:
前面曾提到:当事务提交时会调用fsync对redo log进行刷盘;这是默认情况下的策略,修改innodb_flush_log_at_trx_commit参数可以改变该策略,但事务的持久性将无法保证。
除了事务提交时,还有其他刷盘时机:如master thread每秒刷盘一次redo log等,这样的好处是不一定要等到commit时刷盘,commit速度大大加快。
事务的隔离级别
单个事务一般没啥问题,但多个事务并行执行就可能会出现问题,并行出现问题就需要进行隔离。
有三种隔离级别:读未提交、不可重复读、可重复读(默认是可重复读)
读未提交:最低的隔离级别,下面三种情况都能出现
不可重复读:只允许读取已提交的数据,可避免脏读(避免读了更新但未提交的内容)
可重复读:读取期间别的事务不能更新(但能新增和删除),可避免不可重复读,但避免不了别的事务的增删(mysql默认的事务隔离级别就是可重复读)
串行化:可避免幻读,最高隔离级别,即两个事务不能同时修改底层数据,必须串行等待,没法并发,这种隔离级别很少使用,吞吐量太低,用户体验差
事务A和事务B,事务A在操作数据库时,事务B只能排队等待
脏读:读了别的事务更新但为提交的数据,别的事务回滚后,就读了假数据
不可重复读:多次读的数据不一样(被别的事务修改了内容)
幻读:多次读的数据量不一样(被别的事务新增或删除了一些内容)
MVCC
MySQL默认采用的是可重复读,简称RR,其可解决脏读、不可重复读的问题,使用的是MVCC:全称Multi-Version Concurrency Control,即多版本的并发控制协议。下面的例子很好的体现了MVCC的特点:在同一时刻,不同的事务读取到的数据可能是不同的(即多版本)——在T5时刻,事务A和事务C可以读取到不同版本的数据。
MVCC最大的优点是读不加锁,因此读写不冲突,并发性能好。InnoDB实现MVCC,多个版本的数据可以共存,主要基于以下技术及数据结构:
1)隐藏列:InnoDB中每行数据都有隐藏列,隐藏列中包含了本行数据的事务id、指向undo log的指针等。
2)基于undo log的版本链:前面说到每行数据的隐藏列中包含了指向undo log的指针,而每条undo log也会指向更早版本的undo log,从而形成一条版本链(多版本并存)。
3)ReadView:通过隐藏列和版本链,MySQL可以将数据恢复到指定版本;但是具体要恢复到哪个版本,则需要根据ReadView来确定。所谓ReadView,是指事务(记做事务A)在某一时刻给整个事务系统(trx_sys)打快照,之后再进行读操作时,会将读取到的数据中的事务id与trx_sys快照比较,从而判断数据对该ReadView是否可见,即对事务A是否可见。
简单来说,就是根据隐藏列中的事物id选择版本链中不同的版本(其余事务修改的数据在不同的时期对本事务的可见性是不同的,比如事务B修改但未提交,A事务中通过某种规则判断就不可见这个修改,就避免了脏读和不可重复读,具体规则待学习)
事务与底层数据
即只有事务成功提交,才会把最终的数据落盘
在事务进行过程中,未结束之前,DML语句是不会更改底层数据,只是将历史操作记录一下,在内存中完成记录。只有在事物结束的时候,而且是成功的结束的时候,才会修改底层硬盘文件中的数据(说明别的事务读取时读的是内存中的数据?)
上面部分内容转载于:https://www.cnblogs.com/kismetv/p/10331633.html 感谢!
分布式事务
当单个数据库不够用时,就可能对数据库进行水平分区,如分库分表,多机房,这时单库的ACID已经很难适应这种情况,而整个集群的ACID是很难追求的,一味地追求集群的ACID会使得系统变得很差,此时:CAP理论出现了。
CAP
cap理论指的是:一致性(Consistency)、可用性和分区容忍性三个要素最多同时满足两个,而其中的分区容忍性又是不可或缺的。
一致性模型
数据一致性可分为三类:
强一致性:数据更新成功后,任意时刻所有副本中的数据都是一致的,一般采用同步的方式实现。
弱一致性:数据更新后,系统不承诺立即返回最新写入的值,也不承诺什么时候能返回(和最终一致性的区别是啥?)。
最终一致性:数据更新后,系统不承诺立即返回最新写入的值,但是保证最终会返回上一次更新操作的值。
在上面的事务的基础上