MySQL的InnoDB的细粒度行锁,是它最吸引人的特性之一。

但是,如《InnoDB,5项最佳实践》所述,如果查询没有命中索引,也将退化为表锁。

InnoDB的细粒度锁,是实现在索引记录上的。

一,InnoDB的索引

InnoDB的索引有两类索引,聚集索引(Clustered Index)与普通索引(Secondary Index)。

InnoDB的每一个表都会有聚集索引:

(1)如果表定义了PK,则PK就是聚集索引;

(2)如果表没有定义PK,则第一个非空unique列是聚集索引;

(3)否则,InnoDB会创建一个隐藏的row-id作为聚集索引;

为了方便说明,后文都将以PK说明。

索引的结构是B+树,这里不展开B+树的细节,说几个结论:

(1)在索引结构中,非叶子节点存储key,叶子节点存储value;

(2)聚集索引,叶子节点存储行记录(row);

画外音:所以,InnoDB索引和记录是存储在一起的,而MyISAM的索引和记录是分开存储的。

(3)普通索引,叶子节点存储了PK的值;

画外音:

所以,InnoDB的普通索引,实际上会扫描两遍:

第一遍,由普通索引找到PK;

第二遍,由PK找到行记录;

索引结构,InnoDB/MyISAM的索引结构,如果大家感兴趣,未来撰文详述。

举个例子,假设有InnoDB表:

t(id PK, name KEY, sex, flag);

表中有四条记录:

1, shenjian, www.tianjiuyule178.com m, A

3, zhangsan, www.leyou2.net m, A

5, lisi, m, A

9, wangwu, f, B

以看到:

(1)第一幅图,id PK的聚集索引,叶子存储了所有的行记录;

(2)第二幅图,name上的普通索引,叶子存储了PK的值;

对于:

select * from t where name=’shenjian’;

(1)会先在name普通索引上查询到PK=1;

(2)再在聚集索引衫查询到(1,shenjian, m, A)的行记录;

下文简单介绍InnoDB七种锁中的剩下三种:

  • 记录锁(Record Locks)

  • 间隙锁(Gap Locks)

  • 临键锁(Next-Key Locks)

为了方便讲述,如无特殊说明,后文中,默认的事务隔离级别为可重复读(Repeated Read, RR)。

二、记录锁(Record Locks)

记录锁,它封锁索引记录,例如:

select * from t where id=1www.yongshiyule178.com for update;

它会在id=1的索引记录上加锁,以阻止其他事务插入,更新,删除id=1的这一行。

需要说明的是:

select * from t where id=1;

则是快照读(SnapShot Read),它并不加锁,具体在《InnoDB为什么并发高,读取快?》中做了详细阐述。

三、间隙锁(Gap Locks)

间隙锁,它封锁索引记录中的间隔,或者第一条索引记录之前的范围,又或者最后一条索引记录之后的范围。

依然是上面的例子,InnoDB,RR:

t(id PK, name KEY, sex, flag);

表中有四条记录:

1, shenjian, m, A

3, zhangsan, m, A

5, lisi, m, A

9, wangwu, f, B

这个SQL语句

select * from t

where id between 8 and 15

for update;

会封锁区间,以阻止其他事务id=10的记录插入。

画外音:

为什么要阻止id=10的记录插入?

如果能够插入成功,头一个事务执行相同的SQL语句,会发现结果集多出了一条记录,即幻影数据。

间隙锁的主要目的,就是为了防止其他事务在间隔中插入数据,以导致“不可重复读”。

如果把事务的隔离级别降级为读提交(Read Committed, RC),间隙锁则会自动失效。

四、临键锁(Next-Key Locks)

临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间。

更具体的,临键锁会封锁索引记录本身,以及索引记录之前的区间。

如果一个会话占有了索引记录R的共享/排他锁,其他会话不能立刻在R之前的区间插入新的索引记录。

画外音:原文是说

If one session has a shared or exclusive lock on record R in an index, another session cannot insert a new index record in the gap immediately before R in the index order.

依然是上面的例子,InnoDB,RR:

t(id PK, name KEY, sex, flag);

表中有四条记录:

1, shenjian, m, A

3, zhangsan, m, A

5, lisi, m, A

9, wangwu, f, B

PK上潜在的临键锁为:

(-infinity, 1]

(1, 3]

(3, 5]

(5, 9]

(9, +infinity]

临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。

画外音:关于事务的隔离级别,以及幻读,之前的文章一直没有展开说明,如果大家感兴趣,后文详述。

今天的内容,主要对InnoDB的索引,以及三种锁的概念做了介绍。场景与例子,也都是最简单的场景与最简单的例子。

InnoDB的锁,与索引类型,事务的隔离级别相关,更多更复杂更有趣的案例,后续和大家介绍。

五、总结

(1)InnoDB的索引与行记录存储在一起,这一点和MyISAM不一样;

(2)InnoDB的聚集索引存储行记录,普通索引存储PK,所以普通索引要查询两次;

(3)记录锁锁定索引记录;

(4)间隙锁锁定间隔,防止间隔中被其他事务插入;

(5)临键锁锁定索引记录+间隔,防止幻读;

MySQL的InnoDB的细粒度行锁,是它最吸引人的特性之一。的更多相关文章

  1. [MySQL]InnoDB引擎的行锁和表锁

    1.行锁和表锁 在mysql 的 InnoDB引擎支持行锁,与Oracle不同,mysql的行锁是通过索引加载的,即是行锁是加在索引响应的行上的,要是对应的SQL语句没有走索引,则会全表扫描, 行锁则 ...

  2. Innodb中的行锁与表锁

    在Innodb引擎中既支持行锁也支持表锁,那么什么时候会锁住整张表,什么时候或只锁住一行呢? InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块 ...

  3. MySQL(27):行锁、表锁、乐观锁、悲观锁

    1. 首先说一下:行锁 和 表锁  主要是针对锁粒度划分的. 一般分为:行锁.表锁.库锁 (1)行锁:访问数据库的时候,锁定整个行数据,防止并发错误. (2)表锁:访问数据库的时候,锁定整个表数据,防 ...

  4. MySQL Internal - InnoDB存储引擎(行结构)

    InnoDB行存储的三个组成部分(说明: F字符表示列的数量) 名称(Name) 大小(Size) Field Start Offsets (F*1) or (F*2) bytes Extra Byt ...

  5. Mysql InnoDB行锁实现方式(转)

    Mysql InnoDB行锁实现方式 InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的.InnoDB这种行锁实现特点 ...

  6. Mysql InnoDB行锁实现方式

    Mysql InnoDB行锁实现方式 InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的.InnoDB这种行锁实现特点 ...

  7. Mysql研磨之InnoDB行锁模式

    事务并发带来的一些问题 (1)更新丢失(LostUpdate):当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题最后的更新覆盖了由其 ...

  8. Mysql InnoDB下的两种行锁

    今天例举2种常见的Mysql InnoDB下的行锁 现有表dr_test(id pk, name) 数据是 1 zhangsan2 lisi3 wangwu 例子1 事务1 update dr_tes ...

  9. mysql:InnoDB行/表级锁实现/事务

    转载:http://book.51cto.com/art/200803/68127.htm 20.3.4 InnoDB行锁实现方式 InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL ...

随机推荐

  1. python 补缺收集

    [http://www.cnblogs.com/happyframework/p/3255962.html] 1. 高效code 与 不常用的函数用法: #带索引的遍历 , )): print(ind ...

  2. 第七章 动态创建HTML内容

    javascript也可以改变网页的结构和内容 document.write()方法 可以方便快捷地把字符串插入到文档里 document.write("<strong>hell ...

  3. 复习C++_指针、动态分配内存

    注意:++i指的是先计算i+1,然后将其赋给i cout<<str[7]<<endl; //输出a 注:交换失败 注意:delete释放之后,变为迷途指针. 注:n--> ...

  4. python_52_函数返回值2

    def test1(x,y): print(x,y) test1(1,2)#位置参数调用,按顺序来,与形参一一对应 test1(y=1,x=2)#输出为2 1,不是1 2.关键字参数调用按关键字,不按 ...

  5. C++ ADL

    即在一个名称作为调用运算符的左操作数时,并且这个名字是一个无限定名称时,在无限定查找到的名字集合中额外增加的一个规则使集合范围扩大(从而可以定位到其他一些限定名称),通常是用来保证定义在不同命名空间的 ...

  6. c++异常处理--创建自己的异常处理类

    复习了一下c++中的异常处理! 继承exception类 class myException : public std::exception { public: explicit myExceptio ...

  7. 14.2-ELK 经典用法—企业自定义日志收集切割和mysql模块

    本文收录在Linux运维企业架构实战系列 一.收集切割公司自定义的日志 很多公司的日志并不是和服务默认的日志格式一致,因此,就需要我们来进行切割了. 1.需切割的日志示例 2018-02-24 11: ...

  8. python变量声明及简单数据类型

    一.python声明变量 变量的命名和使用 # 在Python中使用变量时,需要遵守一些规则和指南. # 违反这些规则将引发错误,而指南旨在让你编写的代码更容易阅读和理解.请务必牢记下述有关变量的规则 ...

  9. JavaScript对象创建的九种方式

    1.标准创建对象模式 var person = new Object(); person.name = "Nicholas"; person.age = 29; person.jo ...

  10. 科学计算库Numpy——数组生成

    等差数组 使用np.arange()或np.linspace()生成元素是等差数列的数组. 以10为底的数组 使用np.logspace()生成元素是以10为底的数组. 数组扩展 使用np.meshg ...