回来写博客,少年前端时间被django迷了心魄
1 什么是MVCC
MVCC全称是: Multiversion concurrency control,多版本并发控制,提供并发访问数据库时,对事务内读取的到的内存做处理,用来避免写操作堵塞读操作的并发问题。
举个例子,程序员A正在读数据库中某些内容,而程序员B正在给这些内容做修改(假设是在一个事务内修改,大概持续10s左右),A在这10s内 则可能看到一个不一致的数据,在B没有提交前,如何让A能够一直读到的数据都是一致的呢?
有几种处理方法,第一种: 基于锁的并发控制,程序员B开始修改数据时,给这些数据加上锁,程序员A这时再读,就发现读取不了,处于等待情况,只能等B操作完才能读数据,这保证A不会读到一个不一致的数据,但是这个会影响程序的运行效率。还有一种就是:MVCC,每个用户连接数据库时,看到的都是某一特定时刻的数据库快照,在B的事务没有提交之前,A始终读到的是某一特定时刻的数据库快照,不会读到B事务中的数据修改情况,直到B事务提交,才会读取B的修改内容。
一个支持MVCC的数据库,在更新某些数据时,并非使用新数据覆盖旧数据,而是标记旧数据是过时的,同时在其他地方新增一个数据版本。因此,同一份数据有多个版本存储,但只有一个是最新的。
MVCC提供了 时间一致性的 处理思路,在MVCC下读事务时,通常使用一个时间戳或者事务ID来确定访问哪个状态的数据库及哪些版本的数据。读事务跟写事务彼此是隔离开来的,彼此之间不会影响。假设同一份数据,既有读事务访问,又有写事务操作,实际上,写事务会新建一个新的数据版本,而读事务访问的是旧的数据版本,直到写事务提交,读事务才会访问到这个新的数据版本。
MVCC有两种实现方式,第一种实现方式是将数据记录的多个版本保存在数据库中,当这些不同版本数据不再需要时,垃圾收集器回收这些记录。这个方式被PostgreSQL和Firebird/Interbase采用,SQL Server使用的类似机制,所不同的是旧版本数据不是保存在数据库中,而保存在不同于主数据库的另外一个数据库tempdb中。第二种实现方式只在数据库保存最新版本的数据,但是会在使用undo时动态重构旧版本数据,这种方式被Oracle和MySQL/InnoDB使用。
2 Innodb的MVCC
在Innodb db中,无论是聚簇索引,还是二级索引,每一行记录都包含一个 DELETE bit,用于表示该记录是否被删除, 同时,聚簇索引还有两个隐藏值:DATA_TRX_ID,DATA_ROLL_PTR。DATA _TRX_ID表示产生当前记录项的事务ID,这个ID随着事务的创建不断增长;DATA _ROLL_PTR指向当前记录项的undo信息。
- 无论是聚簇索引,还是二级索引,只要其键值更新,就会产生新版本。将老版本数据deleted bti设置为1;同时插入新版本。
- 对于聚簇索引,如果更新操作没有更新primary key,那么更新不会产生新版本,而是在原有版本上进行更新,老版本进入undo表空间,通过记录上的undo指针进行回滚。
- 对于二级索引,如果更新操作没有更新其键值,那么二级索引记录保持不变。
- 对于二级索引,更新操作无论更新primary key,或者是二级索引键值,都会导致二级索引产生新版本数据。
- 聚簇索引设置记录deleted bit时,会同时更新DATA_TRX_ID列。老版本DATA_TRX_ID进入undo表空间;二级索引设置deleted bit时,不写入undo。
MVCC只工作在REPEATABLE READ和READ COMMITED隔离级别下。READ UNCOMMITED不是MVCC兼容的,因为查询不能找到适合他们事务版本的行版本;它们每次都只能读到最新的版本。SERIABLABLE也不与MVCC兼容,因为读操作会锁定他们返回的每一行数据 。
在MVCC中,读操作分为两类:当前读跟快照读,当前读返回最新记录,会加锁,保证该记录不会被其他事务修改;快照读,读取的是记录的某个版本(有可能是最新版本也有可能是旧版本),不加锁。
快照读:RU,RC,RR隔离级别下,select * from tbname where ....
当前读:
- select * from tbname where .... for update (加X锁)
- select * from tbname where .... lock in share mode(加S锁)
- insert into tbname .... (加X锁,注意如果有unique key的情况)
- delete from tbname ... (加X锁)
- update tbname set ... where .. (加X锁)
3 Two Phase Locking
2-PL,也就是两阶段锁,锁的操作分为两个阶段:加锁、解锁。先加锁,后解锁,不相交。加锁时,读操作会申请并占用S锁,写操作会申请并占用X锁,如果对所在记录加锁有冲突,那么会处于等待状态,知道加锁成功才惊醒下一步操作。解锁时,也就是事务提交或者回滚的时候,这个阶段会释放该事务中所有的加锁情况,进行一一释放锁。
假设事务对记录A和记录B都有操作,那么,其加锁解锁按照逐行加锁解锁顺序,如下:
BEGIN
LOCK A
READ A
A:A+100
WRITE A
UNLOCK A
LOCK B
READ B
UNLOCK B
COMMIT
两阶段锁还有几种特殊情况:conservative(保守)、strict(严格)、strong strict(强严格),这三种类型在加锁和释放锁的处理有些不一样。
- conservative
- 在事务开始的时候,获取需要的记录的锁,避免在操作期间逐个申请锁可能造成的锁等待,conservative 2PL 可以避免死锁
- strict
- 仅在事务结束的时候(commit or rollback),才释放所有 write lock,read lock 则正常释放
- strong strict
- 仅在事务结束的时候(commit or rollback),才释放所有锁,包括write lock 跟 read lock 都是结束后才释放。
4 数据不一致情况
4.1 脏读
读取未提交事务中修改的数据,称为脏读。
举例,表格 A (name,age),记录1为name='xinysu',age=188
这里,事务2 中读出来的数据是 (name,age)=('xinysu',299),这一条是 事务1中未提交的记录,属于脏数据。
4.2 丢失更新
多个更新操作并发执行,导致某些更新操作数据丢失。
举例,表格 A (name,age),记录1为name='xinysu',age=188。并发2个更新操作如下:
正常情况下,如果是事务1操作后,age为288,事务2再进行288+100=388,但是实际上,事务2的操作覆盖事务1的操作,造成了事务1的更新丢失。
4.3 不可重复读
同个事务多次读取同一条存在的记录,但是读取的结构不一致,称之为不可重复读。
举例,表格 A (name,age),记录1为name='xinysu',age=188。操作如下:
事务1第一次读出来的结构是name='xinysu',age=188,第二次读出来的结果是name='xinysu',age=288,同个事务中,多次读取同一行存在的记录,但结果不一致的情况,则为不可重复读。
4.4 幻读
同个事务多次读取某段段范围内的数据,但是读取到底行数不一致的情况,称之为幻读。
举例,表格 A (name,age),记录1为name='xinysu',age=188。操作如下:
事务1中,第一次读取的结果行数有1行,如果事务2执行的是delete,则事务1第二次读取的为0行;如果事务2执行的是INSERT,则事务2第二次读取的行数是2行,前后记录数不一致,称之为幻读。
5 innodb的隔离级别
5.1 隔离级别介绍
- Read Uncommited
- 简称 RU,读未提交记录,始终是读最新记录
- 不支持快照读,都是当前读
- 可能存在脏读、不可重复读、幻读等问题;
- Read Commited
- 简称 RC ,读已提交记录
- 支持快照读,读取版本有可能不是最新版本
- 支持当前读,读取到的记录添加锁
- Read Repeatable
- 简称 RR ,可重复读记录
- 支持快照读,读取版本有可能不是最新版本
- 支持当前读,读取到的记录添加锁,并且对读取的范围枷锁,保证满足查询条件的记录不能够被insert进来
- 不存在脏读、不可重复读及幻读情况;
- Read Serializable
- 简称 RS,序列化读记录
- 不支持快照读,都是当前读
- select数据添加S锁,update\insert\delete数据添加X锁
- 并发度最差,除非明确业务需求及性能影响,才使用,一般不建议在innodb中应用
5.2 隔离级别测试
测试各个隔离级别下的数据不一致情况。
1.查看当前会话隔离级别
select @@tx_isolation;
2.查看系统当前隔离级别
select @@global.tx_isolation;
3.设置当前会话隔离级别
set session transaction isolation level repeatable read;
4.设置系统当前隔离级别
set global transaction isolation level repeatable read;
5.2.1 Read Uncommitted
所有事务隔离级别设置: set session transaction isolation level read Uncommited ;
该隔离级别没有的快照读,所有读操作都是读最新版本,可以读未提交事务的数据。
测试1:update数据不提交,另起查询
测试结果:正常select可以查询到不提交的事务内容,属于脏读
测试2:修改数据不提交,另起事务多次查询
测试结果:同个事务多次读取同一行记录结果不一致,属于重复读
测试3:INSERT数据不提交,另起事务多次查询
测试结果:同个事务多次读取相同范围的数据,但是行数不一样,属于幻读
测试4:不同事务对同一行数据进行update
测试结果:由于INNODB有锁机制,所有所有update都会持有X锁互斥,并不会出现事务都提交成功情况下的丢失更新,所以四个隔离级别都可以避免丢失更新问题。
总结:没有快照读,都是当前读,所有读都是读可以读未提交记录,存在脏读、不可重复读、幻读等问题。
5.2.2 Read Committed
所有事务隔离级别设置: set session transaction isolation level read committed ;
由于该隔离级别支持快照读,不添加for update跟lock in share mode的select 查询语句,使用的是快照读,读取已提交记录,不添加锁。所以测试使用当前读的模式测试,添加lock in share mode,添加S锁。
测试1:update数据不提交,另起查询
测试结果:由于当前读持有S锁,导致update申请X锁处于等待情况,无法更新,同个事务内的多次查询结果一致,无脏读及不可重复读情况。
测试2:INSERT数据不提交,另起事务多次查询
测试结果:同个事务多次读取相同范围的数据,但是行数不一样,属于幻读(这里注意,如果insert 分为beigin;commit,一直不commit的话,3的查询会处于等待情况,因为它需要申请的S锁被 insert的X锁所堵塞)
测试3:快照读测试
测试结果:同个事务多次读取相同记录,读取的都是已提交记录,不存在脏读及丢失更新情况,但是存在不可重复读及幻读。
总结:支持快照读,快照读 不存在脏读及丢失更新情况,但是存在不可重复读及幻读;而当前读不存在脏读、不可重复读问题,存在幻读问题。
5.2.3 Read Repeatable
所有事务隔离级别设置: set session transaction isolation level repeatable read ;
由于该隔离级别支持快照读,不添加for update跟lock in share mode的select 查询语句,使用的是快照读,不添加锁。所以测试使用当前读的模式测试,添加lock in share mode,添加S锁。
测试1:update数据不提交,另起查询
测试结果:由于当前读持有S锁,导致update申请X锁处于等待情况,无法更新,同个事务内的多次查询结果一致,无脏读及不可重复读情况。
测试2:INSERT数据不提交,另起事务多次查询
测试结果:同个事务多次读取相同范围的数据,会有GAP锁锁定,故同个事务多次当前读结果记录数都是一致的,不存在幻读情况。
测试3:快照读测试
测试结果:同个事务多次读取相同记录,不存在脏读及丢失更新、不可重复读及幻读等情况。
总结:支持快照读,快照读跟当前读不存在脏读、不可重复读问题、幻读问题。
5.2.4 Read Serializable
所有事务隔离级别设置: set session transaction isolation level Serializable ;
该隔离级别不支持快照读,所有SELECT查询都是当前读,并且持有S锁.
测试1:update数据不提交,另起查询;INSERT数据不提交,另起事务多次查询
测试结果:该隔离级别下所有select语句持有S锁,导致update申请X锁处于等待情况,INSERT申请X也被堵塞,同个事务内的多次查询结果一致,不存在脏读、不可重复读及幻读情况。
总结:无快照读,所有SELECT查询都是当前读,不存在脏读、不可重复读问题、幻读问题。
以为没了,not,还有一个概念这里没有提交,这里补充介绍下:semi-consistent read
PS: semi-consistent read
在read committed或者read uncommitted 隔离级别下,有这样的测试现象:
测试表格及数据
CREATE TABLE `tblock` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
insert into tblock(name) select 'su';
insert into tblock(name) select 'xin';
测试1:两个update事务并发,分别update不同行,update条件列无索引
测试结果:两条update互不干扰,正常执行。
测试2:update语句不提交,另起事务当前读操作
测试结果:当前读被堵塞,无法正常加X锁
问题点:为啥两个测试中的sql序号2,都是申请X锁,测试1可以正常申请情况,而测试2不行呢?
正常情况下,where条件中的name列没有索引,故这个update操作是对全表做scan扫描加X锁,正常情况下,在第一个事务中,update语句没有提交的情况下,这个表格有一个表锁X,对每一行数据都无法申请S锁或者X锁,那么为什么 测试1 可以正常申请呢?
在这里,需要引入semi-constent-read,半一致性读。官网解释如下:
semi consistent read:
A type of read operation used for UPDATE statements, that is a combination of read committed and consistent read. When an UPDATE statement examines a row that is already locked, InnoDB returns the latest committed version to MySQL so that MySQL can determine whether the row matches the WHERE condition of the UPDATE. If the row matches (must be updated), MySQL reads the row again, and this time InnoDB either locks it or waits for a lock on it. This type of read operation can only happen when the transaction has the read committed isolation level, or when the innodb_locks_unsafe_for_binlog option is enabled.
semi-consistent read是update语句在读数据的一种操作, 是read committed与consistent read两者的结合。update语句A在没有提交时,另外一个update语句B读到一行已经被A加锁的记录,但是这行记录不在A的where条件内,此时InnoDB返回记录最近提交的版本给B,由MySQL上层判断此版本是否满足B的update的where条件。若满足(需要更新),则MySQL会重新发起一次读操作,此时会读取行的最新版本(并加锁)。semi-consistent read只会发生在read committed及read uncommitted隔离级别,或者是参数innodb_locks_unsafe_for_binlog被设置为true。 对update起作用,对select insert delete 不起作用。这就导致了update 不堵塞,但是当前读的select则被堵塞的现象。
发生 semi consitent read的条件:
- update语句
- 执行计划时scan,range scan or table scan,不能时unique scan
- 表格为聚集索引
总结如下:
- mysql的并发处理机制_下篇
MySQL的并发处理机制,有MVCC及锁机制来处理,上篇简要说明了 MVCC及隔离级别,这篇来说说mysql下的锁. 温馨提示:下文有几个表格长度较长,右下角的博文导航目录会挡道,浏览 ...
- 说一说MySQL的锁机制
锁概述 MySQL的锁机制,就是数据库为了保证数据的一致性而设计的面对并发场景的一种规则. 最显著的特点是不同的存储引擎支持不同的锁机制,InnoDB支持行锁和表锁,MyISAM支持表锁. 表锁就是把 ...
- 关于MySQL的锁机制详解
锁概述 MySQL的锁机制,就是数据库为了保证数据的一致性而设计的面对并发场景的一种规则. 最显著的特点是不同的存储引擎支持不同的锁机制,InnoDB支持行锁和表锁,MyISAM支持表锁. 表锁就是把 ...
- mysql的Replication机制
mysql的Replication机制 参考文档:http://www.doc88.com/p-186638485596.html Mysql的 Replication 是一个异步的复制过程. 从上图 ...
- MySQL- 锁机制及MyISAM表锁
锁是计算机协调多个进程或线程并发访问某一资源的机制.在数据库中,除传统的计算资源(如CPU.RAM.I/O等)的争用以外,数据也是一种供许 多用户 共享的资源.如何保证数据并发访问的一致性.有效性是所 ...
- MysqL自动提交机制的关闭
MysqL在执行一句数据库操作命令的时候,通常都是自动提交的.常用引擎下有两种,分别是MyIsam和InnoDB,MyIsam是不支持事务处理的,但InnoDB支持,但InnoDB在不开启事务处理的情 ...
- MySQL的安全机制
MySQL的安全机制: 1.MySQL登录 mysql -u账户 -p密码 -h127.0.0.1 -P端口 数据库名 mysql -h hostname|hostIP -p port -u user ...
- MySQL的复制机制
MySQL的复制机制 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.MySQL复制介绍 1>.MySQL复制允许将主实例(master)上的数据同步到一个或多个从实例( ...
- MySQL中间件方案盘点_搜狐科技_搜狐网
MySQL中间件方案盘点_搜狐科技_搜狐网
随机推荐
- js数组及数组应用(冒泡和二分,遍历输出)
一.定义:1)var arr=new Array(); 加数据:arr[0]=1; 2)定义同时赋值:var arr=new Array(1,2,3,4,5); 3)调用:var arr=new Ar ...
- Eclipse导出JavaDoc中文乱码问题解决
在Eclipse里 export 选 JavaDoc,在向导的最后一页的Extra JavaDoc Options 里填上参数即可 比如项目采用的是UTF-8的编码就填:-encoding UTF-8 ...
- 页面实现多个定时器(计时器)时选用NSTimer还是GCD?(干货不湿)
定时器在我们每个人做的iOS项目里面必不可少,如登录页面倒计时.支付期限倒计时等等,一般来说使用NSTimer创建定时器: + (NSTimer *)timerWithTimeInterval:(NS ...
- AutoMapper5.0创建对象方法更新
/// <summary> /// 单个对象映射 /// </summary> public static TDestination MapTo<TSource, TDe ...
- Spring定时任务实例
一.Quartz介绍 在企业应用中,我们经常会碰到时间任务调度的需求,比如每天凌晨生成前天报表,每小时生成一次汇总数据等等.Quartz是出了名的任务调度框架,它可以与J2SE和J2EE应用程序相结合 ...
- PHP+MySql实现后台数据的读取
我们使用的是PHP 的php_mysqli扩展 首先了解一些基础的用法 1.连接数据库使用 mysqli_connect() 参数:①主机地址 ②MYSQL用户名 ③MYSQL密码 ④选择 ...
- 怎么样刷新frameset的整个页面
<a href="main.html?a=2" target="_parent">? 设置a链接的target属性值为_parent即可
- 【hexo】如何在一个小时内搭载个人博客
版权申明:本文为博主原创文章,未经博主允许不得转载.如需转载,请私聊博主. 什么是hexo Hexo是一个开源的静态博客生成器,用node.js开发,作者是台湾大学生tommy351. 前期准备 安装 ...
- accp8.0转换教材第3章MySQL高级查询(一)理解与练习
一.单词部分 ①constraint约束②foreign外键③references参考 ④subquery子查询⑤inner内部的⑥join连接 二.预习部分 1.修改表SQL语句的关键字是什么 RE ...
- [Paper Reading]--Exploiting Relevance Feedback in Knowledge Graph
<Exploiting Relevance Feedback in Knowledge Graph> Publication: KDD 2015 Authors: Yu Su, Sheng ...