(本文为了方便,英文关键词都都采用小写方式,相关知识点会简单介绍,争取做到可以独立阅读)

文章开始我会先介绍本文需要的知识点如下:

  • innodb的聚簇索引(聚集索引)和非聚簇索引(二级索引、非聚集索引)的知识
  • innodb的隔离级别(isolation level)
  • 简单的sql知识(能读懂sql语句)
  • MVCC(Multi-Version Concurrent Control)多版本并发控制
  • 数据的脏读、幻读(如果有时间会详细讲一下脏读如果没时间,网上讲这个地方的也很多)
 
问题1:读有几种模式、加锁有几种方式
我们先看一个mysql表和几条语句
表名称:my_table  
搜索引擎:innodb
表结构:
 
1. select * from my_table where id = 1;
2. select * from my_table where id = 1 lock in share mode;
3. select * from my_table where id = 1 for update;
4. update my_table set address = 'tianjin' where id = 1;
先说隔离级别,mysql隔离级别分为四种:
未提交读(read uncommitted)、提交读(read committed)、重复读(repeatable read)、序列化(serializable)
其中mysql默认的隔离级别为重复读(repeatable read),以下简称为rr,本文也只介绍这种模式
读的模式分为两种:
  • 快照读(snapshot read)
  • 当前读(current read)
我们先来了解一下MVCC:
MVCC是为了实现数据库的并发控制而设计的一种协议。与其相对的事LBCC即基于锁的并发控制(Lock-Based Concurrent Control)。要实现数据库的并发访问控制,最简单的做法就是加锁访问,即读的时候不能写(这个读为当前读,后面介绍。允许多个线程同时对想读的内容加锁,即共享锁或叫S锁),写的时候不能读(只能有一个线程对同一内容进行写操作,即排它锁,X锁)。这样的加锁访问,其实并不算是真正的并发,或者说它只能实现并发的读,既读写串行化,这样就大大降低了数据库的读写性能。
LBCC是四种隔离级别中级别最高的Serialize隔离级别。MVCC对比LBCC它的最大好处便是,读不加锁,读写不冲突。在MVCC中,读操作可以分成两类,快照读(Snapshot read)和当前读(current read)。快照读,读取的是记录的可见版本(可能是历史版本,即最新的数据可能正在被当前执行的事务并发修改),不会对返回的记录加锁,如上面的sql语句1;而当前读,读取的是记录的最新版本,并且会对返回的记录加锁,保证其他事务不会并发修改这条记录。如上面的sql语句2,3,4。不同的是2加的是s锁,3、4加的是x锁,insert加的也是x锁。
注:MVCC只在RC和RR两个隔离级别下工作,其他两个隔离级别都和MVCC不兼容
加锁的方式:(未涉及意向锁)
先看一个sql语句
update my_table set name ='zhang' where id = 1;

假设id为主键:此条sql执行的时候会给此行数据加x锁,如下图

mysql的innodb默认的隔离模式为RR模式,既可重复读,Innodb的RR隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),因此不存在幻读现象。但是标准的RR只能保证在同一事务中多次读取同样记录的结果是一致的,而无法解决幻读(不保证在事务中出现)问题。Innodb的幻读解决是依靠MVCC的实现机制做到的。其他模式以后有时间再加在这里不对其他模式做讲解。
 
这里因为id为主键,innodb中在主键上存在聚簇索引,其他的索引均为二级索引,这里做一下简单介绍
 
聚簇索引:在innodb存储引擎中,主键的存在至关重要,及时你不为表设置主键,存储引擎也会隐式的定义一个主键,只是对用户来说透明。之所以说他重要,是因为聚簇索引的存储是和数据存储在一起的,而聚簇索引的数据就是数据存储的顺序。如果需要查找的数据是连续的,那么按照聚簇索引查找到的数据位置也是连续的,只需要按顺序读取就可以。对于聚集索引,叶子结点即存储了真实的数据行,不再有另外单独的数据页(这里和后面的二级索引有区别,二级索引的叶子节点指向数据页数据行的逻辑指针,需要按照指针再去检索数据)。 在一张表上最多只能创建一个聚集索引,因为真实数据的物理顺序只能有一种。
二级索引:表数据存储顺序与索引顺序无关。对于非聚集索引,叶结点包含索引字段值及指向数据页数据行的逻辑指针,其行数量与数据表行数据量一致。
看上面的sql语句,或者看之前的几条sql,这个语句执行的时候会给这条记录加x锁,这时候如果其他事务中的语句也在进行锁的操作(既更新、插入或者删除,以及2语句当前读操作加的s锁)就会造成锁争用(innodb出现锁争用的时候处理方式为回滚超时获取不到锁的事务)。
 
我们先按照上面四条语句两条并发时的相互影响的情况来
 
情况1:id为主键
1. select * from my_table where id = 1;
2. select * from my_table where id = 1 lock in share mode;

我们上面说过,语句1为快照读,对其他的读或者写没有影响。所以这两条语句并行时,1读快照,2为语句加s锁。

 
select * from my_table where id = 1 lock in share mode;
情况2:id为主键
2. select * from my_table where id = 1 lock in share mode;
3. select * from my_table where id = 1 for update;

其中语句2加s锁,3加x锁(在数据被加s锁的时候,其他的给这条想要读取这条记录也需要给这条记录加s锁,这就是为什么s锁是共享锁。此时是不允许再给这条记录加x锁的)两种锁是不能同时存在在一条记录上的。所以两条语句不能并发执行。

情况3:id为主键

3. select * from my_table where id = 1 for update;
4. update my_table set address = 'tianjin' where id = 1;
这种情况下两条语句都需要给数据加x锁,所以显然不能并发执行。
 

下面我们来讨论一下id不为主键的情况

id若不为主键,则不能使用聚簇索引,而在innodb中有一下几种情况
  • 二级唯一索引
  • 二级不唯一索引
  • 没有索引
由于只要不是快照读则一定会加锁,我们已经了解了锁的形式,则不难明白不论是先加x锁还是s锁哪一种,都一定不能再加另一种锁,所以我们下面只分析加锁的方式
 
情况4:假设id为二级唯一索引(unique)
 
4. update my_table set address = 'tianjin' where id = 1;
这里很明显需要加x锁,但是这里的加锁和id为主键(索引为聚簇索引)的情况加锁不完全一样,会稍微复杂一点。
 

这个时候我们需要对索引知识有一定的了解,上面说过二级索引中的叶子节点存储的除了索引信息还有到实际数据的逻辑指针,也就是说我们需要现在二级唯一索引中查找到这条记录的逻辑指针,然后通过指针去查找到数据实际的存储位置并给这条数据加锁。注意,这里的加锁应该是加在了索引上和数据本身上(或者说是聚簇索引上也可以,因为两者是存储在一个结构中的,而innodb中二级索引叶子中的逻辑指针并不是数据存储的物理地址,而是主键值)而不只是二级唯一索引上。如果想用好innodb,他的索引结构是必须要学习的,如果以后有时间会详细介绍。

 
情况5:age为二级非唯一索引,id为主键
 
5. update my_table set address = 'tianjin' where age = 25;

此种情况比前一种情况更特殊,因为情况3和4都只能找到一条记录,只需要对这条记录加锁,则不会发生结果集被修改的情况。但是如果age为二级非唯一索引,我们看到如下表格中有两条记录age=25

 
如果我们在update的过程中,有一个用户插入了一条age也为25的数据,那么就是发生一种现象,你明明更新了所有的age=25的数据,但是执行完了却有一条数据没有更新的幻觉,这就是幻读(可以自行查找资料,避免本文过长)。这个时候显然只给查找出的数据加锁是解决不了这个问题的。所以就有了gap锁(间隙锁字面上可能更好理解)这里需要画图大家理解一下:
如图这里在age为25的有两个 ,id分别为1和3。我们在修改执行上面语句的时候,如果没有gap锁,则可能发生一种情况:另一个事务执行如下语句
update my_table set age=25 where id=2;

则发生幻读现象。gap锁可以防止在语句或者事务执行过程中有满足条件的记录插入进来造成幻读。所以说在此种情况下,除了给满足条件的二级索引和数据(或聚簇索引)加x锁之外还要给相关的间隙加锁。可以理解为这个加gap锁是在二级索引的范围内防止新的索引项加入,因为二级索引本身也是有序的。

情况6:age上无索引,id为主键
 
5. update my_table set address = 'tianjin' where age = 25;
这种情况下,所有记录都被加上了X锁,每条记录间的间隙(GAP)也同时被加上了GAP锁。在实际情况中可能sql更复杂,如果用到了其他列索引,mysql会按照索引顺序筛选结果,也许并不会进行全表加x锁和gap锁。
 
 注:转自自己的博客
 
 
 
 

关于InnoDB的读写锁类型以及加锁方式的更多相关文章

  1. Linux程序设计学习笔记----多线程编程线程同步机制之相互排斥量(锁)与读写锁

    相互排斥锁通信机制 基本原理 相互排斥锁以排他方式防止共享数据被并发訪问,相互排斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个相互排斥锁逻辑上绑定之后,对该资源的訪问操作例如以下: ...

  2. pthread中读写锁

    读写锁很像一个互斥量,他阻止多个线程同时修改共享数据的另一种方法,区分不同互斥量的是他是分读数据和写数据,一个读写锁允许同时多个线程读数据,只要他们不修改数据. 只要没有写模式下的加锁,任意线程都可以 ...

  3. C基础 读写锁中级剖析

    引言 读写锁 是为了 解决, 大量 ''读'' 和 少量 ''写'' 的业务而设计的. 读写锁有3个特征: 1.当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞 2.当 ...

  4. Linux 自旋锁,互斥量(互斥锁),读写锁

    自旋锁(Spin Lock) 自旋锁类似于互斥量,不过自旋锁不是通过休眠阻塞进程,而是在取得锁之前一直处于忙等待的阻塞状态.这个忙等的阻塞状态,也叫做自旋. 自旋锁通常作为底层原语实现其他类型的锁. ...

  5. 嵌入式 Linux线程同步读写锁rwlock示例

    读写锁比mutex有更高的适用性,可以多个线程同时占用读模式的读写锁,但是只能一个线程占用写模式的读写锁.1. 当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞:2. ...

  6. Linux的线程同步对象:互斥量Mutex,读写锁,条件变量

        进程是Linux资源分配的对象,Linux会为进程分配虚拟内存(4G)和文件句柄等 资源,是一个静态的概念.线程是CPU调度的对象,是一个动态的概念.一个进程之中至少包含有一个或者多个线程.这 ...

  7. Java 并发包中的读写锁及其实现分析

    1. 前言 在Java并发包中常用的锁(如:ReentrantLock),基本上都是排他锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时 刻可以允许多个读线程访问,但是在写线程访问时,所有 ...

  8. linux中读写锁的rwlock介绍-nk_ysg-ChinaUnix博客

    linux中读写锁的rwlock介绍-nk_ysg-ChinaUnix博客 linux中读写锁的rwlock介绍 2013-02-26 13:59:35 分类: C/C++   http://yaro ...

  9. UNIX环境高级编程——线程同步之读写锁以及属性

    读写锁和互斥量(互斥锁)很类似,是另一种线程同步机制,但不属于POSIX标准,可以用来同步同一进程中的各个线程.当然如果一个读写锁存放在多个进程共享的某个内存区中,那么还可以用来进行进程间的同步, 互 ...

随机推荐

  1. nyoj 复杂度

    复杂度 时间限制:1000 ms  |  内存限制:65535 KB 难度:3   描述 for(i=1;i<=n;i++) for(j=i+1;j<=n;j++) for(k=j+1;k ...

  2. OO第一次总结

    第一次作业: 第一次作业的指导书发下来之后我按着上面的步骤一步一步的做了之后发现项目拉下来了,怎么开始码代码呢...然后在舍友的帮助下才知道怎么建包建类,然后对Java的语法又不是很了解,于是就先把C ...

  3. 数据结构-线性表的链式存储相关算法(C语言实现)

    链表的简单介绍 为什么需要线性链表 当然是为了克服顺序表的缺点,在顺序表中,做插入和删除操作时,需要大量的移动元素,导致效率下降. 线性链表的分类 按照链接方式: 按照实现角度: 线性链表的创建和简单 ...

  4. 第三章 JavaScript操作BOM对象

    第三章   JavaScript操作BOM对象 一.window对象 浏览器对象模型(BOM)是javascript的组成之一,它提供了独立与浏览器窗口进行交换的对象,使用浏览器对象模型可以实现与HT ...

  5. 搭建一个web服务下载HDFS的文件

    需求描述 为了能方便快速的获取HDFS中的文件,简单的搭建一个web服务提供下载很方便快速,而且在web服务器端不留临时文件,只做stream中转,效率相当高! 使用的框架是SpringMVC+HDF ...

  6. Zepto.js库touch模块代码解析

    Zepto.js也许并不陌生,专门针对移动端开发,Zepto有一些基本的触摸事件可以用来做触摸屏交互(tap事件.swipe事件),Zepto是不支持IE浏览器的. 下面来解析一些Zepto.js触摸 ...

  7. Swagger: 一个restful接口文档在线生成+功能测试软件

    一.什么是 Swagger? Swagger 是一款RESTFUL接口的文档在线自动生成+功能测试功能软件.Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTful 风格的 ...

  8. python入门(7)Python程序的风格

    python入门(7)Python程序的风格 Python采用缩进方式,写出来的代码就像下面的样子: # print absolute value of an integer: a = 100 if ...

  9. [转]安卓新一代多渠道打包工具Walle 解决渠道包V2签名问题

    转自https://www.jianshu.com/p/572b59829a08 为什么要打多个渠道的包? 大家都知道,android应用商店大大小小有几百个,作为一个有志向的app,就需要做到统计各 ...

  10. zipline-benchmarks.py文件改写

    改写原因:在这个模块中的 get_benchmark_returns() 方法回去谷歌财经下载对应SPY(类似于上证指数)的数据,但是Google上下载的数据在最后写入Io操作的时候会报一个恶心的编码 ...