(9)MySQL进阶篇SQL优化(InnoDB锁-记录锁)
1.概述
InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则InnoDB将使用表锁!在实际应用程序中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。
2. InnoDB行锁实现方式
2.1InnoDB存储引擎的表在不使用索引时使用表锁例子
创建一个临时表:
MySQL [(none)]> CREATE TABLE goods.tab_no_index (ID INT,Name VARCHAR(50));
Query OK, 0 rows affected (0.02 sec)
插入三条测试数据:
MySQL [(none)]> INSERT INTO goods.tab_no_index (ID,`Name`) VALUES (1,'1'),(2,'2'),(3,'3');
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
session_1 |
session_2 |
(1)先设置事务T1提交类型为事务非自动提交。 |
(1)先设置事务T2提交类型为事务非自动提交。 |
MySQL [(none)]> SET AUTOCOMMIT=0; Query OK, 0 rows affected (0.00 sec) |
MySQL [(none)]> SET AUTOCOMMIT=0; Query OK, 0 rows affected (0.00 sec) |
(2)在当前事务T1中查询tab_no_index表的数据行ID=1数据。 |
(2)在当前事务T2中查询tab_no_index表的数据行ID=2数据。 |
MySQL [(none)]> SELECT * FROM goods.tab_no_index WHERE ID=1; +------+------+ | ID | Name | +------+------+ | 1 | 1 | +------+------+ 1 row in set (0.00 sec) |
MySQL [(none)]> SELECT * FROM goods.tab_no_index WHERE ID=2; +------+------+ | ID | Name | +------+------+ | 2 | 2 | +------+------+ 1 row in set (0.00 sec) |
(3)在当前事务T1中为tab_no_index表的数据行ID=1加上排他锁。 |
|
MySQL [(none)]> SELECT * FROM goods.tab_no_index WHERE ID=1 FOR UPDATE; +------+------+ | ID | Name | +------+------+ | 1 | 1 | +------+------+ 1 row in set (0.00 sec) |
|
(3)在当前事务T2中为tab_no_index表的数据行ID=2加上排他锁,会发生阻塞超时。 |
|
MySQL [(none)]> SELECT * FROM goods.tab_no_index WHERE ID=2 FOR UPDATE; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction |
从上述表格的例子来看,session_1只给ID=1数据行加了排他锁,但session_2在请求ID=2的数据行排他锁时,却出现了锁等待!
2.2InnoDB存储引擎的表在使用索引时使用行锁例子
创建一个临时表:
MySQL [(none)]> CREATE TABLE goods.tab_with_index (ID INT,Name VARCHAR(50));
Query OK, 0 rows affected (0.02 sec)
建立临时表ID列索引:
MySQL [(none)]> ALTER TABLE goods.tab_with_index ADD INDEX Index_ID(ID);
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
插入三条测试数据:
MySQL [(none)]> INSERT INTO goods.tab_with_index (ID,`Name`) VALUES (1,'1'),(2,'2'),(3,'3');
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
session_1 |
session_2 |
(1)先设置事务T1提交类型为事务非自动提交。 |
(1)先设置事务T2提交类型为事务非自动提交。 |
MySQL [(none)]> SET AUTOCOMMIT=0; Query OK, 0 rows affected (0.00 sec) |
MySQL [(none)]> SET AUTOCOMMIT=0; Query OK, 0 rows affected (0.00 sec) |
(2)在当前事务T1中查询tab_with_index表的数据行ID=1数据。 |
(2)在当前事务T2中查询tab_with_index表的数据行ID=2数据。 |
MySQL [(none)]> SELECT * FROM goods.tab_with_index WHERE ID=1; +------+------+ | ID | Name | +------+------+ | 1 | 1 | +------+------+ 1 row in set (0.00 sec) |
MySQL [(none)]> SELECT * FROM goods.tab_with_index WHERE ID=2; +------+------+ | ID | Name | +------+------+ | 2 | 2 | +------+------+ 1 row in set (0.00 sec) |
(3)在当前事务T1中为tab_with_index表的数据行ID=1加上排他锁。 |
|
MySQL [(none)]> SELECT * FROM goods.tab_with_index WHERE ID=1 FOR UPDATE; +------+------+ | ID | Name | +------+------+ | 1 | 1 | +------+------+ 1 row in set (0.00 sec) |
|
(3)在当前事务T2中为tab_with_index表的数据行ID=2加上排他锁,却并没有发生阻塞超时。 |
|
MySQL [(none)]> SELECT * FROM goods.tab_with_index WHERE ID=2 FOR UPDATE; +------+------+ | ID | Name | +------+------+ | 2 | 2 | +------+------+ 1 row in set (0.00 sec) |
该示例同样跟2小节示例一样,只是ID列加了索引,而session_2在请求ID=2的数据行却没有阻塞!
2.3小结
通过以上两个示例可以了解到:
●在ID列没有建立索引的情况下,InnoDB没有使用到行锁,而是使用到表锁。
●在ID列建立索引的情况下,InnoDB使用到行锁,而是没有使用到表锁。
也就是说,InnoDB存储引擎的表列如果在没有加索引情况下查询,使用到是表锁而不是行锁,会产生阻塞情况,这在并发情况下是灾难的。
4.记录锁
由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同记录行,但是如果是使用相同的索引键,是会出现锁冲突的。下面我们通过两个示例来了解下。
4.1InnoDB存储引擎使用相同索引键的阻塞例子
这个示例还是沿用tab_with_index表做演示,ID是非聚集索引列,Name列没有索引,有以下数据:
MySQL [(none)]> SELECT * FROM goods.tab_with_index;
+------+------+
| ID | Name |
+------+------+
| 1 | 1 |
| 1 | 2 |
| 3 | 3 |
+------+------+
3 rows in set (0.00 sec)
session_1 |
session_2 |
(1)先设置事务T1提交类型为事务非自动提交。 |
(1)先设置事务T2提交类型为事务非自动提交。 |
MySQL [(none)]> SET AUTOCOMMIT=0; Query OK, 0 rows affected (0.00 sec) |
MySQL [(none)]> SET AUTOCOMMIT=0; Query OK, 0 rows affected (0.00 sec) |
(2)在当前事务T1中为tab_with_index表的数据行ID=1 AND `Name`='1' 加上排他锁。 |
|
MySQL [(none)]> SELECT * FROM goods.tab_with_index WHERE ID=1 |
|
(2)在当前事务T2中为tab_with_index表的数据行ID=1 AND `Name`='2' 加上排他锁,发生阻塞超时。 |
|
MySQL [(none)]> SELECT * FROM goods.tab_with_index WHERE ID=1 |
|
(3)在当前事务T2中为tab_with_index表插入以下一行数据,并没有发生阻塞超时。 |
|
MySQL [(none)]> INSERT INTO goods.tab_with_index (ID,`Name`) VALUES (4,'4'); Query OK, 1 row affected (0.00 sec) |
从上面示例可以看到,当session_1锁定事务中ID=1记录行时,会阻止session_2事务获取该记录行,而当插入一条ID=4数据时,却没有发生阻塞,成功插入!也就是说当索引数据加上记录锁时,会阻止其他事务对该表该行数据(例如ID=1记录行)进行插入,更新和删除操作。
4.2InnoDB存储引擎的表使用不同索引的阻塞例子
这个示例还是沿用tab_with_index表做演示,ID为主键索引列,Name为非聚集索引列,有以下数据:
MySQL [(none)]> SELECT * FROM goods.tab_with_index;
+----+------+
| ID | Name |
+----+------+
| 1 | 1 |
| 2 | 2 |
+----+------+
2 rows in set (0.00 sec)
session_1 |
session_2 |
(1)先设置事务T1提交类型为事务非自动提交。 |
(1)先设置事务T2提交类型为事务非自动提交。 |
MySQL [(none)]> SET AUTOCOMMIT=0; Query OK, 0 rows affected (0.00 sec) |
MySQL [(none)]> SET AUTOCOMMIT=0; Query OK, 0 rows affected (0.00 sec) |
(2)在当前事务T1中为tab_with_index表的数据行ID=1 加上排他锁。 |
|
MySQL [(none)]> SELECT * FROM goods.tab_with_index WHERE ID=1 FOR UPDATE; +----+------+ | ID | Name | +----+------+ | 1 | 1 | +----+------+ 1 row in set (0.00 sec) |
|
(2)由于tab_with_index表ID=1的记录行在session_1事务中被锁定,当在session_2事务查询Name=’2’记录行时,因为该记录行并不属于ID=1范围记录行之中,所以可以获得tab_with_index表的锁。 |
|
MySQL [(none)]> SELECT * FROM goods.tab_with_index WHERE `Name`='2' FOR UPDATE; +----+------+ | ID | Name | +----+------+ | 2 | 2 | +----+------+ 1 row in set (0.00 sec) |
|
(3)同理,由于访问的Name=’1’记录已经被 session_1事务中被锁定,所以只能等待获得tab_with_index表的锁。 |
|
MySQL [(none)]> SELECT * FROM goods.tab_with_index WHERE `Name`='1' FOR UPDATE; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction |
当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
5.总结
其实在现实生产环境,即便在条件中使用了索引字段,但是决定是否使用索引来检索数据记录行是由MySQL执行计划来决定的,所以如果MySQL认为全表扫描效率更高,那么它就会优先执行全表扫描操作。比如一些很小的表,哪怕您在条件中使用了索引列,它也不会使用索引,这种情况下InnoDB将会使用表锁,而不是使用行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。还有一种情况在之前索引章节也有说过,如果检索值的数据类型与索引字段值不同,虽然MySQL能够进行数据类型转换,但是却不会使用索引,从而导致InnoDB使用表锁。例如tab_with_index表的name字段有索引,但是name字段是varchar类型的,如果where条件中值是int等值类型,那么就不是和varchar类型进行比较,而会对name进行类型转换,从而要不全表或遍历索引树扫描获取记录行,如下面语句:
-- 全表或遍历索引树扫描
EXPLAIN SELECT * FROM goods.tab_with_index WHERE `Name`=1;
-- 走索引扫描
EXPLAIN SELECT * FROM goods.tab_with_index WHERE `Name`='1';
参考文献:
深入浅出MySQL大全
(9)MySQL进阶篇SQL优化(InnoDB锁-记录锁)的更多相关文章
- (6)MySQL进阶篇SQL优化(MyISAM表锁)
1.MySQL锁概述 锁是计算机协调多个进程或线程并发访问某一资源的机制.在数据库中,除传统的计算资源 (如 CPU.RAM.I/O 等)的抢占以外,数据也是一种供许多用户共享的资源.如何保证数 据并 ...
- (11)MySQL进阶篇SQL优化(InnoDB锁问题排查与解决)
1.概述 前面章节之所以介绍那么多锁的知识点和示例,其实最终目的就是为了排查与解决死锁的问题,下面我们把之前学过锁知识重温与补充一遍,然后再通过例子演示下如果排查与解决死锁. 2.前期准备 ●数据库事 ...
- (7)MySQL进阶篇SQL优化(InnoDB锁-事务隔离级别 )
1.概述 在我们在学习InnoDB锁知识点之前,我觉得有必要让大家了解它的背景知识,因为这样才能让我们更系统地学习好它.InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION ...
- (10)MySQL进阶篇SQL优化(InnoDB锁-间隙锁)
1.概述 当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁:对于键值在条件范围内但并不存在的记录,叫做"间隙(GAP)&quo ...
- (8)MySQL进阶篇SQL优化(InnoDB锁-共享锁、排他锁与意向锁)
1.锁的分类 锁(Locking)是数据库在并发访问时保证数据一致性和完整性的主要机制.之前MyISAM锁章节已经讲过锁分类,而InnoDB锁按照粒度分为锁定整个表的表级锁(table-level l ...
- (4)MySQL进阶篇SQL优化(常用SQL的优化)
1.概述 前面我们介绍了MySQL中怎么样通过索引来优化查询.日常开发中,除了使用查询外,我们还会使用一些其他的常用SQL,比如 INSERT.GROUP BY等.对于这些SQL语句,我们该怎么样进行 ...
- (2)MySQL进阶篇SQL优化(show status、explain分析)
1.概述 在应用系统开发过程中,由于初期数据量小,开发人员写SQL语句时更重视功能上的实现,但是当应用系统正式上线后,随着生产数据量的急剧增长,很多SQL语句开始逐渐显露出性能问题,对生产环境的影响也 ...
- (3)MySQL进阶篇SQL优化(索引)
1.索引问题 索引是数据库优化中最常用也是最重要的手段之一,通过索引通常可以帮助用户解决大多数 的SQL性能问题.本章节将对MySQL中的索引的分类.存储.使用方法做详细的介绍. 2.索引的存储分类 ...
- (5)MySQL进阶篇SQL优化(优化数据库对象)
1.概述 在数据库设计过程中,用户可能会经常遇到这种问题:是否应该把所有表都按照第三范式来设计?表里面的字段到底改设置为多大长度合适?这些问题虽然很小,但是如果设计不当则可能会给将来的应用带来很多的性 ...
随机推荐
- Codeforces Round #538 D. Lunar New Year and a Wander
题面: 传送门 题目描述: Bob想在公园散步.公园由n个点和m条无向边组成.当Bob到一个未经过的点时,他就会把这个点的编号记录在笔记本上.当且仅当Bob走完所有的点,他才会停下来.这时,Bob的笔 ...
- JVM之调优及常见场景分析
JVM调优 GC调优是最后要做的工作,GC调优的目的可以总结为下面两点: 减少对象晋升到老年代的数量 减少FullGC的执行时间 通过监控排查问题及验证优化结果,可以分为: 命令监控:jps.jinf ...
- [换根DP][倍增]luogu P5666 树的重心
题面 https://www.luogu.com.cn/problem/P5666 分析 对于一棵以i为根的树来说,它的重心必然在其size大于等于sumsize/2的子树中. 那么断掉一条边e(u, ...
- Android Studio 之 通过 Intent 完成点击按钮实现页面跳转
•Intent 简介 Intent 是 Android 程序中各组件之间进行交互的一种重要方式: 它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据. Intent 有多个构造函数,其 ...
- javascript 取自己
var own=docment.currentScript;
- Kubernetes,kubectl常用命令详解
kubectl概述 祭出一张图,转载至 kubernetes-handbook/kubectl命令概述 ,可以对命令族有个整体的概念. 环境准备 允许master节点部署pod,使用命令如下: kub ...
- 全网最详细的Linux命令系列-cat命令
cat命令的用途是连接文件或标准输入并打印.这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用. 命令格式: cat [选项] [文件].. ...
- HTML5和CSS3提高
一.HTML5的新特性 HTML5 的新增特性主要是针对于以前的不足,增加了一些新的标签.新的表单和新的表单属性等. 这些新特性都有兼容性问题,基本是 IE9+ 以上版本的浏览器才支持,如果不考虑兼容 ...
- (5)MySQL进阶篇SQL优化(优化数据库对象)
1.概述 在数据库设计过程中,用户可能会经常遇到这种问题:是否应该把所有表都按照第三范式来设计?表里面的字段到底改设置为多大长度合适?这些问题虽然很小,但是如果设计不当则可能会给将来的应用带来很多的性 ...
- 在ASP.NET Core中使用ViewComponent
前言 在之前的开发过程中,我们对于应用或者说使用一些小的组件,通常使用分布页(partial view),再往前在Web Form中我们会进行应用WEB Control,好吧提及一个关键性代码TagP ...