mysql 通过测试'for update',深入了解行锁、表锁、索引
mysql 通过测试'for update',深入了解行锁、表锁、索引
条件
FOR UPDATE 仅适用于InnoDB存储引擎,且必须在事务区块(BEGIN/COMMIT)中才能生效。
mysql默认情况下每个sql都是单独的一个事务,并且是自动提交事务。
测试之前需要设置成非自动提交事务,不然无法模拟并发访问:
mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 1 |
+--------------+
1 row in set (0.00 sec)
mysql> set autocommit = 0;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 0 |
+--------------+
1 row in set (0.00 sec)
此修改只针对当前窗口有效,重新打开的新窗口依然是自动提交事务的
所以要就需要两个窗口,窗口a:非自动提交事务,用于for update操作;
窗口b:用于普通update操作。
测试
我们有一数据库 test1,有一张表testa ,有自增主键ID,name,id_card
表中有两条数据
mysql> select * from testa;
+----+-------+--------------------+
| id | name | id_card |
+----+-------+--------------------+
| 1 | wangb | 322343256564545754 |
| 2 | shuna | 320990348823998792 |
+----+-------+--------------------+
2 rows in set (0.00 sec)
mysql> desc testa;
+---------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(10) | NO | | NULL | |
| id_card | varchar(18) | YES | UNI | NULL | |
+---------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
1.只明确主键
- 有数据
在a窗口进行开启事务,对id为1的数据进行 for update,此时并没有commit;
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from testa where id = 1 for update;
+----+------+--------------------+
| id | name | id_card |
+----+------+--------------------+
| 1 | wang | 322343256564545754 |
+----+------+--------------------+
1 row in set (0.00 sec)
mysql>
在b窗口对id=1的数据进行update name操作,发现失败:等待锁释放超时
mysql> update testa set name = "wangwang" where id = 1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
再对id=2的数据进行update name操作,发现成功
mysql> update testa set name = "shunshun" where id = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
a窗口commit;之后,b窗口update操作都显示正常
- 无数据
a窗口 select for update 无数据
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from testa where id = 3
-> ;
Empty set (0.00 sec)
mysql>
b窗口,对两条数据update操作都是成功
mysql> update testa set name = "wanga" where id = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update testa set name = "shun" where id = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
得出结论
明确主键并且有数据的情况下:mysql -> row lock;
明确主键无数据的情况下:mysql -> no lock;
2.明确主键和一个普通字段
- 有数据
将数据还原之后,
在a窗口进行开启事务,对id=1,name='wang'的数据进行 for update,此时并没有commit;
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from testa where id=1 and name = 'wang' for update
-> ;
+----+------+--------------------+
| id | name | id_card |
+----+------+--------------------+
| 1 | wang | 322343256564545754 |
+----+------+--------------------+
1 row in set (0.03 sec)
mysql>
b窗口,对进行for update的那条数据的update操作无效(等待锁释放超时),其他的行的update操作正常
mysql> update testa set name = "wanga" where id = 1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update testa set name = "shunshun" where id = 2;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
a窗口commit;之后,b窗口update操作都显示成功
- 无数据
同第一种情况的无数据测试
得出结论
明确主键和一个普通字段有数据的情况下:mysql -> row lock;
明确主键和一个普通字段无数据的情况下:mysql -> no lock;
3.明确一个普通字段
- 有数据
将数据还原之后,
在a窗口进行开启事务,对name='wang'的数据进行 for update,此时并没有commit;
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from testa where name = 'wang' for update;
+----+------+--------------------+
| id | name | id_card |
+----+------+--------------------+
| 1 | wang | 322343256564545754 |
+----+------+--------------------+
1 row in set (0.00 sec)
mysql>
b窗口,对进行for update的那条数据的update操作失败(等待锁释放超时),其他的行的update操作也显示失败(等待锁释放超时)
mysql> update testa set id_card = '222' where id = 1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update testa set id_card = '333' where id = 2;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
a窗口commit;之后,b窗口update操作都显示成功
- 无数据
同第一种情况的无数据测试
得出结论
只明确一个普通字段有数据的情况下:mysql -> table lock;
只明确一个普通字段无数据的情况下:mysql -> no lock;
4.明确一个unique字段
- 有数据
将数据还原之后,
在a窗口进行开启事务,对id_card='111'的数据进行 for update,此时并没有commit;
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from testa where id_card='111' for update;
+----+------+---------+
| id | name | id_card |
+----+------+---------+
| 1 | wang | 111 |
+----+------+---------+
1 row in set (0.00 sec)
mysql>
b窗口,对进行for update的那条数据的update操作失败(等待锁释放超时),其他的行的update操作显示正常!!
mysql> update testa set id_card = '222' where id = 1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update testa set id_card = '333' where id = 2;
Query OK, 1 row affected (0.00 sec)
- 无数据
同第一种情况的无数据测试
得出结论
只明确一个unique字段有数据的情况下:mysql -> row lock;
只明确一个unique字段无数据的情况下:mysql -> no lock;
思考
为什么对主键和unique字段进行for update操作的时候,mysql进行的是row lock;而对普通字段for update操作的时候进行的是table lock,是根据什么判断呢?
primary key和unique的共同特点是mysql会自动为其创建索引,他们都有索引,那把name字段创建索引,是不是就进行row lock呢?
查看表中的索引:
mysql> show keys from testa\G;
*************************** 1. row ***************************
Table: testa
Non_unique: 0
Key_name: PRIMARY
Seq_in_index: 1
Column_name: id
Collation: A
Cardinality: 2
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
*************************** 2. row ***************************
Table: testa
Non_unique: 0
Key_name: id_card
Seq_in_index: 1
Column_name: id_card
Collation: A
Cardinality: 2
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
2 rows in set (0.00 sec)
ERROR:
No query specified
发现testa表中的索引只包含了id,id_card
添加name字段的索引
mysql> alter table testa add index index_name (name);
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
查看建表语句:
mysql> show create table testa \G;
*************************** 1. row ***************************
Table: testa
Create Table: CREATE TABLE `testa` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) NOT NULL,
`id_card` varchar(18) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_card` (`id_card`),
KEY `index_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
ERROR:
No query specified
发现name字段已经创建了普通索引index_name
在a窗口,对name字段再进行一次for update测试,不commit
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from testa where name = 'wang' for update;
+----+------+---------+
| id | name | id_card |
+----+------+---------+
| 1 | wang | 222 |
+----+------+---------+
1 row in set (0.01 sec)
mysql>
在b窗口 对进行for update的数据进行update操作失败(锁释放等待超时)
mysql> update testa set id_card = '111' where id = 1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
在b窗口 对其他行数据进行update操作,成功!!!
mysql> update testa set id_card = '4353' where id = 2;
Query OK, 1 row affected (0.02 sec)
Rows matched: 1 Changed: 1 Warnings: 0
a窗口commit之后,在b敞口操作正常
总结
select .... for update; 操作
未获取到数据的时候,mysql不进行锁 (no lock)
获取到数据的时候,进行对约束字段进行判断,存在有索引的字段则进行row lock
否则进行 table lock
注意
当使用 '<>','like'等关键字时,进行for update操作时,mysql进行的是table lock
网上其他博客说是因为主键不明确造成的,其实并非如此;
mysql进行row lock还是table lock只取决于是否能使用索引,而 使用'<>','like'等操作时,索引会失效,自然进行的是table lock;
什么情况索引会失效:
1.负向条件查询不能使用索引
负向条件有:!=、<>、not in、not exists、not like 等。
2.索引列不允许为null
单列索引不存null值,复合索引不存全为null的值,如果列允许为 null,可能会得到不符合预期的结果集。
3.避免使用or来连接条件
应该尽量避免在 where 子句中使用 or 来连接条件,因为这会导致索引失效而进行全表扫描,虽然新版的MySQL能够命中索引,但查询优化耗费的 CPU比in多。
4.模糊查询
前导模糊查询不能使用索引,非前导查询可以。
以上情况索引都会失效,所以进行for update的时候,会进行table lock
参考:https://juejin.im/post/5b14e0fd6fb9a01e8c5fc663
再思考
为什么存在索引,mysql进行row lock,不存在索引,mysql进行table lock?
这是存储引擎InnoDB特性决定的:
InnoDB这种行锁实现特点意味者:只有通过索引条件检索数据,InnoDB才会使用行级锁,否则,InnoDB将使用表锁!
再总结
在上述例子中 ,我们使用给name字段加索引的方法,使表锁降级为行锁,不幸的是这种方法只针对 属性值重复率低 的情况。当属性值重复率很高的时候,索引就变得低效,MySQL 也具有自动优化 SQL 的功能。低效的索引将被忽略。就会使用表锁了。
参考:http://zhoupq.com/MySQL-避免行锁升级为表锁——使用高效的索引/
mysql 通过测试'for update',深入了解行锁、表锁、索引的更多相关文章
- MySQL 行锁 表锁机制
MySQL 表锁和行锁机制 行锁变表锁,是福还是坑?如果你不清楚MySQL加锁的原理,你会被它整的很惨!不知坑在何方?没事,我来给你们标记几个坑.遇到了可别乱踩.通过本章内容,带你学习MySQL的行锁 ...
- mysql 行锁 表锁
MySQL数据库 - 引擎: - innodb - 支持事务 - 锁 - 行锁 - 表锁 - 示例: - 终端: begin; select xx from xx for update; commit ...
- MySQL锁(表锁,行锁,共享锁,排它锁,间隙锁)使用详解
锁,在现实生活中是为我们想要隐藏于外界所使用的一种工具.在计算机中,是协调多个进程或县城并发访问某一资源的一种机制.在数据库当中,除了传统的计算资源(CPU.RAM.I/O等等)的争用之外,数据也是一 ...
- MySQL优化篇系列文章(二)——MyISAM表锁与InnoDB锁问题
我可以和面试官多聊几句吗?只是想... MySQL优化篇系列文章(基于MySQL8.0测试验证),上部分:优化SQL语句.数据库对象,MyISAM表锁和InnoDB锁问题. 面试官:咦,小伙子,又来啦 ...
- 【MySQL 读书笔记】全局锁 | 表锁 | 行锁
全局锁 全局锁是针对数据库实例的直接加锁,MySQL 提供了一个加全局锁的方法, Flush tables with read lock 可以使用锁将整个表的增删改操作都锁上其中包括 ddl 语句,只 ...
- mysql中的for update
Select…For Update语句的语法与select语句相同,只是在select语句的后面加FOR UPDATE [NOWAIT]子句. 该语句用来锁定特定的行(如果有where子句,就是满足w ...
- [转]MySQL 表锁和行锁机制
本文转自:http://www.cnblogs.com/itdragon/p/8194622.html MySQL 表锁和行锁机制 行锁变表锁,是福还是坑?如果你不清楚MySQL加锁的原理,你会被它整 ...
- MySQL学习之——锁(行锁、表锁、页锁、乐观锁、悲观锁等)
转载. https://blog.csdn.net/mysteryhaohao/article/details/51669741 锁,在现实生活中是为我们想要隐藏于外界所使用的一种工具.在计算机中,是 ...
- MySQL锁(行锁、表锁、页锁、乐观锁、悲观锁等)
锁,在现实生活中是为我们想要隐藏于外界所使用的一种工具.在计算机中,是协调多个进程或县城并发访问某一资源的一种机制.在数据库当中,除了传统的计算资源(CPU.RAM.I/O等等)的争用之外,数据也是一 ...
随机推荐
- IP代理网址
http://www.kuaidaili.com/free/ http://www.66ip.cn/ http://www.xicidaili.com/nn/ http://www.ip3366.ne ...
- PHP知识点
目录 1. PHP函数前面添加@的作用 2. PHP连接MySQL数据库字符集设置 1. 通过PDO扩展连接MySQL数据库 2. 通过mysql扩展连接 3. php查询数据库出现中文乱码 3. 参 ...
- Leetcode: Sliding Window Median
Median is the middle value in an ordered integer list. If the size of the list is even, there is no ...
- MYSQL.版本查看-LINUX
MYSQL.版本查看-LINUX 方式1: 不需登录mysql,登录Linux服务后,执行如下指令: # mysql -V 注意: 那个是大写的V,如果使用小写的v,在root没有设置密码的情况下,就 ...
- C#中类成员的执行顺序
先进行细分: 类的成员分为:字段.属性.方法.构造方法 成员的修饰符:静态成员.实例成员 层次结构:父类.子类 先不考虑继承关系,执行顺序为: 静态字段静态构造方法实例字段实例构造方法属性和方法是在调 ...
- 压力(性能)测试及jmeter的使用
github中标注lab3的文档记录了我的详细的实验过程,有关环境搭建.过程理解.实验结果与遇到的问题等,希望可以帮到初学者. 欢迎交流- https://github.com/fogmisty/So ...
- 快速学习C语言途径,让你少走弯路
1.标准C语言能干什么? 坦白讲,在今天软件已经发展了半个多世纪,单纯的C语言什么都干不了.标准C语言库只提供了一些通用的逻辑运算方法以及字符串处理,当然字符串在C语言看来也是一种操作内存的方法,所以 ...
- 配置php环境的一个nginx.conf
文件:nginx.conf 内容: #user nobody;worker_processes 1; #error_log logs/error.log;#error_log logs/err ...
- Log4j2 日志级别
Log4j2日志级别 级别 在log4j2中, 共有8个级别,按照从低到高为:ALL < TRACE < DEBUG < INFO < WARN < ERROR < ...
- JQuery选择器,动画,事件和DOM操作
JQuery是由JS封装的一些方法,供我们调用,可以快速的实现某些JS功能,实际是JS编写的方法包 将JQuery文件放到JS文件夹下,然后引用到<head></head>中 ...