一:mysql事务
1.事务特性
原子性 : 实现原理(undo log)
一致性 : 实现原理( redo log)
隔离性 :
持久性 : 实现原理 (redo log)
2. 事务隔离级别
读未提交:一个事务可以读取到另一个事务未提交的修改。这会带来脏读、幻读、不可重复读问题。(基本没用)
读已提交:一个事务只能读取另一个事务已经提交的修改。其避免了脏读,但仍然存在不可重复读和幻读问题。
可重复读(mysql 默认隔离级别):同一个事务中多次读取相同的数据返回的结果是一样的。其避免了脏读和不可重复读问题,但幻读依然存在。
串行化:事务串行执行。避免了以上所有问题。
2.1 : mysql为什么选择可重复读作为默认隔离级别, 一般项目中的mysql事务隔离级别是什么?
这个是有历史原因的,当然要从我们的主从复制开始讲起了!
主从复制,是基于什么复制的?
是基于binlog复制的!这里不想去搬binlog的概念了,就简单理解为binlog是一个记录数据库更改的文件吧~
binlog有几种格式?
OK,三种,分别是
- statement:记录的是修改SQL语句
- row:记录的是每行实际数据的变更
- mixed:statement和row模式的混合
那Mysql在5.0这个版本以前,binlog只支持STATEMENT
这种格式!而这种格式在读已提交(Read Commited)这个隔离级别下主从复制是有bug的,因此Mysql将可重复读(Repeatable Read)作为默认的隔离级别!
接下来,就要说说当binlog为STATEMENT
格式,且隔离级别为读已提交(Read Commited)时,有什么bug呢?如下图所示,在主(master)上执行如下事务
此时在主(master)上执行下列语句
select * from test;
输出如下
+---+
| b |
+---+
| 3 |
+---+
1 row in set
但是,你在此时在从(slave)上执行该语句,得出输出如下
Empty set
这样,你就出现了主从不一致性的问题!原因其实很简单,就是在master上执行的顺序为先删后插!而此时binlog为STATEMENT格式,它记录的顺序为先插后删!从(slave)同步的是binglog,因此从机执行的顺序和主机不一致!就会出现主从不一致!
master 选执行删除,后执行插入(删除的操作不影响后面的插入) 但是事务是插入的事务现执行,删除的事务后执行,binlog的记录是在事务提交后开始记录的,所以binlog里就是选插入后删除。
如何解决?
解决方案有两种!
(1)隔离级别设为可重复读(Repeatable Read),在该隔离级别下引入间隙锁。当Session 1
执行delete语句时,会锁住间隙。那么,Ssession 2
执行插入语句就会阻塞住!
(2)将binglog的格式修改为row格式,此时是基于行的复制,自然就不会出现sql执行顺序不一样的问题!奈何这个格式在mysql5.1版本开始才引入。因此由于历史原因,mysql将默认的隔离级别设为可重复读(Repeatable Read),保证主从复制不出问题!
那么,当我们了解完mysql选可重复读(Repeatable Read)作为默认隔离级别的原因后,接下来我们将其和读已提交(Read Commited)进行对比,来说明为什么在互联网项目为什么将隔离级别设为读已提交(Read Commited)!
读已提交: 消耗资源更少,锁冲突概率和死锁概率更小。
二:mysql日志
1.binlog日志:
日志格式:
- statement : 记录的是修改SQL语句
- row:记录的是每行实际数据的变更
- mixed:statement和row模式的混合
场景: 主从复制 恢复数据
2.事务日志:
redo log : 异常宕机或者介质故障后的数据恢复
写入时间:事务中sql语句执行后执行
redo log是InnoDB存储引擎层的日志,又称重做日志文件,用于记录事务操作的变化,记录的是数据修改之后的值,不管事务是否提交都会记录下来。在实例和介质失败(media failure)时,redo log文件就能派上用场,如数据库掉电,InnoDB存储引擎会使用redo log恢复到掉电前的时刻,以此来保证数据的完整性。
在一条更新语句进行执行的时候,InnoDB引擎会把更新记录写到redo log日志中,然后更新内存,此时算是语句执行完了,然后在空闲的时候或者是按照设定的更新策略将redo log中的内容更新到磁盘中,这里涉及到WAL
即Write Ahead logging
技术,他的关键点是先写日志,再写磁盘。
有了redo log日志,那么在数据库进行异常重启的时候,可以根据redo log日志进行恢复,也就达到了crash-safe
。
redo log日志的大小是固定的,即记录满了以后就从头循环写。
undo log: 事务中sql语句执行前执行
undo log主要为事务的回滚服务。在事务执行的过程中,除了记录redo log,还会记录一定量的undo log。undo log记录了数据在每个操作前的状态,如果事务执行过程中需要回滚,就可以根据undo log进行回滚操作。单个事务的回滚,只会回滚当前事务做的操作,并不会影响到其他的事务做的操作。
undo log的存储方式
innodb存储引擎对undo的管理采用段的方式。rollback segment称为回滚段,每个回滚段中有1024个undo log segment。
3. 日志写到磁盘步骤
Innodb进程内存buffer进行数据更新 --> log buffer --> 操作系统buffer(用户态到内核态的切换) --> 写磁盘
三:mvcc
条件:可重复读或者读已提交
InnoDB
是一个 多版本存储引擎:它保留有关已更改行的旧版本的信息,以支持诸如并发和回滚之类的事务功能 。此信息存储在表空间中的数据结构中,该数据结构称为 回滚段(在Oracle中类似的数据结构之后)。InnoDB
使用回滚段中的信息来执行事务回滚中所需的撤消操作。它还使用该信息来构建行的早期版本,以实现 一致的读取。
在内部,InnoDB
向数据库中存储的每一行添加三个字段。一个6字节的DB_TRX_ID
字段表示插入或更新该行的最后一个事务的事务标识符。此外,删除在内部被视为更新,在该更新中,行中的特殊位被设置为将其标记为已删除。每行还包含一个7字节的 DB_ROLL_PTR
字段,称为滚动指针。回滚指针指向写入回滚段的撤消日志记录。如果行已更新,则撤消日志记录将包含在更新行之前重建行内容所必需的信息。一个6字节的DB_ROW_ID
字段包含一个行ID,该行ID随着插入新行而单调增加。如果 InnoDB
自动生成聚集索引,该索引包含行ID值。否则,该 DB_ROW_ID
列不会出现在任何索引中。
mvcc : mysql innodb 存储的是最新的数据,历史数据存储在undo log中。
快照读
比较当前事务版本号和数据库记录的事务版本号,如果大于等于,直接读取记录, 如果当前事务版本号小于数据库事务版本号 则通过行记录中的滚动指针去查找 当前事务版本上一版本的uodo log记录,通过undo log生成历史记录。
当前读
加锁实现 如果通过主键索引或者唯一索引查找 可重复读 和 读已条件 都是加X锁(排他锁)。 主键索引 : 聚簇索引加一个x锁 唯一非主键索引 : 聚簇索引 ,唯一非主键索引 各加一个X锁。
普通索引: 可重复读 聚簇索引: x锁和间隙锁 普通索引:x锁 读已提交 :聚簇索引: x锁 普通索引 :x锁。