MySQL 表锁和行锁机制

行锁变表锁,是福还是坑?如果你不清楚MySQL加锁的原理,你会被它整的很惨!不知坑在何方?没事,我来给你们标记几个坑。遇到了可别乱踩。通过本章内容,带你学习MySQL的行锁,表锁,两种锁的优缺点,行锁变表锁的原因,以及开发中需要注意的事项。还在等啥?经验等你来拿!

MySQL的存储引擎是从MyISAM到InnoDB,锁从表锁到行锁。后者的出现从某种程度上是弥补前者的不足。比如:MyISAM不支持事务,InnoDB支持事务。表锁虽然开销小,锁表快,但高并发下性能低。行锁虽然开销大,锁表慢,但高并发下相比之下性能更高。事务和行锁都是在确保数据准确的基础上提高并发的处理能力。本章重点介绍InnoDB的行锁。

案例分析

目前,MySQL常用的存储引擎是InnoDB,相对于MyISAM而言。InnoDB更适合高并发场景,同时也支持事务处理。我们通过下面这个案例(坑),来了解行锁和表锁。

业务:因为订单重复导入,需要用脚本将订单状态为"待客服确认"且平台是"xxx"的数据批量修改为"已关闭"。

说明:避免直接修改订单表造成数据异常。这里用innodb_lock 表演示InnoDB的行锁。表中有三个字段:id,k(key值),v(value值)。表在github上:https://github.com/ITDragonBlog/daydayup/tree/master/MySQL/

步骤:

第一步:连接数据库,这里为了方便区分命名为Transaction-A,设置autocommit为零,表示需手动提交事务。

第二步:Transaction-A,执行update修改id为1的命令。

第三步:新增一个连接,命名为Transaction-B,能正常修改id为2的数据。再执行修改id为1的数据命令时,却发现该命令一直处理阻塞等待中。

第四步:Transaction-A,执行commit命令。Transaction-B,修改id为1的命令自动执行,等待37.51秒。

总结:多个事务操作同一行数据时,后来的事务处于阻塞等待状态。这样可以避免了脏读等数据一致性的问题。后来的事务可以操作其他行数据,解决了表锁高并发性能低的问题

# Transaction-A
mysql> set autocommit = 0;
mysql> update innodb_lock set v='1001' where id=1;
mysql> commit; # Transaction-B
mysql> update innodb_lock set v='2001' where id=2;
Query OK, 1 row affected (0.37 sec)
mysql> update innodb_lock set v='1002' where id=1;
Query OK, 1 row affected (37.51 sec)

有了上面的模拟操作,结果和理论又惊奇的一致,似乎可以放心大胆的实战。。。。。。但现实真的很残酷。

现实:当执行批量修改数据脚本的时候,行锁升级为表锁。其他对订单的操作都处于等待中,,,

原因:InnoDB只有在通过索引条件检索数据时使用行级锁,否则使用表锁!而模拟操作正是通过id去作为检索条件,而id又是MySQL自动创建的唯一索引,所以才忽略了行锁变表锁的情况。

步骤:

第一步:还原问题,Transaction-A,通过k=1更新v。Transaction-B,通过k=2更新v,命令处于阻塞等待状态。

第二步:处理问题,给需要作为查询条件的字段添加索引。用完后可以删掉。

总结:InnoDB的行锁是针对索引加的锁,不是针对记录加的锁。并且该索引不能失效,否则都会从行锁升级为表锁。索引失效的原因在上一章节中已经介绍:http://www.cnblogs.com/itdragon/p/8146439.html

Transaction-A
mysql> update innodb_lock set v='1002' where k=1;
mysql> commit;
mysql> create index idx_k on innodb_lock(k); Transaction-B
mysql> update innodb_lock set v='2002' where k=2;
Query OK, 1 row affected (19.82 sec)

从上面的案例看出,行锁变表锁似乎是一个坑,可MySQL没有这么无聊给你挖坑。这是因为MySQL有自己的执行计划。

当你需要更新一张较大表的大部分甚至全表的数据时。而你又傻乎乎地用索引作为检索条件。一不小心开启了行锁(没毛病啊!保证数据的一致性!)。可MySQL却认为大量对一张表使用行锁,会导致事务执行效率低,从而可能造成其他事务长时间锁等待和更多的锁冲突问题,性能严重下降。所以MySQL会将行锁升级为表锁,即实际上并没有使用索引。

我们仔细想想也能理解,既然整张表的大部分数据都要更新数据,一行一行地加锁效率则更低。其实我们可以通过explain命令查看MySQL的执行计划,你会发现key为null。表明MySQL实际上并没有使用索引,行锁升级为表锁也和上面的结论一致。

本章重点介绍InnoDB的行锁及其相关的事务知识。如果想了解MySQL的执行计划,请看上一章节

行锁

行锁的劣势:开销大;加锁慢;会出现死锁

行锁的优势:锁的粒度小,发生锁冲突的概率低;处理并发的能力强

加锁的方式:自动加锁。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁;对于普通SELECT语句,InnoDB不会加任何锁;当然我们也可以显示的加锁:

共享锁:select * from tableName where ... + lock in share more

排他锁:select * from tableName where ... + for update

InnoDB和MyISAM的最大不同点有两个:一,InnoDB支持事务(transaction);二,默认采用行级锁。加锁可以保证事务的一致性,可谓是有人(锁)的地方,就有江湖(事务);我们先简单了解一下事务知识。

MySQL 事务属性

事务是由一组SQL语句组成的逻辑处理单元,事务具有ACID属性。

原子性(Atomicity):事务是一个原子操作单元。在当时原子是不可分割的最小元素,其对数据的修改,要么全部成功,要么全部都不成功。

一致性(Consistent):事务开始到结束的时间段内,数据都必须保持一致状态。

隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的"独立"环境执行。

持久性(Durable):事务完成后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

事务常见问题

更新丢失(Lost Update)

原因:当多个事务选择同一行操作,并且都是基于最初选定的值,由于每个事务都不知道其他事务的存在,就会发生更新覆盖的问题。类比github提交冲突。

脏读(Dirty Reads)

原因:事务A读取了事务B已经修改但尚未提交的数据。若事务B回滚数据,事务A的数据存在不一致性的问题。

不可重复读(Non-Repeatable Reads)

原因:事务A第一次读取最初数据,第二次读取事务B已经提交的修改或删除数据。导致两次读取数据不一致。不符合事务的隔离性。

幻读(Phantom Reads)

原因:事务A根据相同条件第二次查询到事务B提交的新增数据,两次数据结果集不一致。不符合事务的隔离性。

幻读和脏读有点类似

脏读是事务B里面修改了数据,

幻读是事务B里面新增了数据。

事务的隔离级别

数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大。这是因为事务隔离实质上是将事务在一定程度上"串行"进行,这显然与"并发"是矛盾的。根据自己的业务逻辑,权衡能接受的最大副作用。从而平衡了"隔离" 和 "并发"的问题。MySQL默认隔离级别是可重复读。

脏读,不可重复读,幻读,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。

+------------------------------+---------------------+--------------+--------------+--------------+
| 隔离级别 | 读数据一致性 | 脏读 | 不可重复 读 | 幻读 |
+------------------------------+---------------------+--------------+--------------+--------------+
| 未提交读(Read uncommitted) | 最低级别 | 是 | 是 | 是 |
+------------------------------+---------------------+--------------+--------------+--------------+
| 已提交读(Read committed) | 语句级 | 否 | 是 | 是 |
+------------------------------+---------------------+--------------+--------------+--------------+
| 可重复读(Repeatable read) | 事务级 | 否 | 否 | 是 |
+------------------------------+---------------------+--------------+--------------+--------------+
| 可序列化(Serializable) | 最高级别,事务级 | 否 | 否 | 否 |
+------------------------------+---------------------+--------------+--------------+--------------+

查看当前数据库的事务隔离级别:show variables like 'tx_isolation';

mysql> show variables like 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+

间隙锁

当我们用范围条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做"间隙(GAP)"。InnoDB也会对这个"间隙"加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。

Transaction-A
mysql> update innodb_lock set k=66 where id >=6;
Query OK, 1 row affected (0.63 sec)
mysql> commit; Transaction-B
mysql> insert into innodb_lock (id,k,v) values(7,'7','7000');
Query OK, 1 row affected (18.99 sec)

危害(坑):若执行的条件是范围过大,则InnoDB会将整个范围内所有的索引键值全部锁定,很容易对性能造成影响

排他锁

排他锁,也称写锁,独占锁,当前写操作没有完成前,它会阻断其他写锁和读锁。

# Transaction_A
mysql> set autocommit=0;
mysql> select * from innodb_lock where id=4 for update;
+----+------+------+
| id | k | v |
+----+------+------+
| 4 | 4 | 4000 |
+----+------+------+
1 row in set (0.00 sec) mysql> update innodb_lock set v='4001' where id=4;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0 mysql> commit;
Query OK, 0 rows affected (0.04 sec)
# Transaction_B
mysql> select * from innodb_lock where id=4 for update;
+----+------+------+
| id | k | v |
+----+------+------+
| 4 | 4 | 4001 |
+----+------+------+
1 row in set (9.53 sec)

共享锁

共享锁,也称读锁,多用于判断数据是否存在,多个读操作可以同时进行而不会互相影响。当如果事务对读锁进行修改操作,很可能会造成死锁。如下图所示。

# Transaction_A
mysql> set autocommit=0;
mysql> select * from innodb_lock where id=4 lock in share mode;
+----+------+------+
| id | k | v |
+----+------+------+
| 4 | 4 | 4001 |
+----+------+------+
1 row in set (0.00 sec) mysql> update innodb_lock set v='4002' where id=4;
Query OK, 1 row affected (31.29 sec)
Rows matched: 1 Changed: 1 Warnings: 0
# Transaction_B
mysql> set autocommit=0;
mysql> select * from innodb_lock where id=4 lock in share mode;
+----+------+------+
| id | k | v |
+----+------+------+
| 4 | 4 | 4001 |
+----+------+------+
1 row in set (0.00 sec) mysql> update innodb_lock set v='4002' where id=4;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

分析行锁定

通过检查InnoDB_row_lock 状态变量分析系统上的行锁的争夺情况 show status like 'innodb_row_lock%'

mysql> show status like 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 0 |
| Innodb_row_lock_time_avg | 0 |
| Innodb_row_lock_time_max | 0 |
| Innodb_row_lock_waits | 0 |
+-------------------------------+-------+

innodb_row_lock_current_waits: 当前正在等待锁定的数量

innodb_row_lock_time: 从系统启动到现在锁定总时间长度;非常重要的参数,

innodb_row_lock_time_avg: 每次等待所花平均时间;非常重要的参数,

innodb_row_lock_time_max: 从系统启动到现在等待最常的一次所花的时间;

innodb_row_lock_waits: 系统启动后到现在总共等待的次数;非常重要的参数。直接决定优化的方向和策略。

行锁优化

1 尽可能让所有数据检索都通过索引来完成,避免无索引行或索引失效导致行锁升级为表锁。

2 尽可能避免间隙锁带来的性能下降,减少或使用合理的检索范围。

3 尽可能减少事务的粒度,比如控制事务大小,而从减少锁定资源量和时间长度,从而减少锁的竞争等,提供性能。

4 尽可能低级别事务隔离,隔离级别越高,并发的处理能力越低。

表锁

表锁的优势:开销小;加锁快;无死锁

表锁的劣势:锁粒度大,发生锁冲突的概率高,并发处理能力低

加锁的方式:自动加锁。查询操作(SELECT),会自动给涉及的所有表加读锁,更新操作(UPDATE、DELETE、INSERT),会自动给涉及的表加写锁。也可以显示加锁:

共享读锁:lock table tableName read;

独占写锁:lock table tableName write;

批量解锁:unlock tables;

共享读锁

对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读操作,但会阻塞对同一表的写操作。只有当读锁释放后,才能执行其他进程的写操作。在锁释放前不能取其他表。

Transaction-A
mysql> lock table myisam_lock read;
Query OK, 0 rows affected (0.00 sec) mysql> select * from myisam_lock;
9 rows in set (0.00 sec) mysql> select * from innodb_lock;
ERROR 1100 (HY000): Table 'innodb_lock' was not locked with LOCK TABLES mysql> update myisam_lock set v='1001' where k='1';
ERROR 1099 (HY000): Table 'myisam_lock' was locked with a READ lock and can't be updated mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
Transaction-B
mysql> select * from myisam_lock;
9 rows in set (0.00 sec) mysql> select * from innodb_lock;
8 rows in set (0.01 sec) mysql> update myisam_lock set v='1001' where k='1';
Query OK, 1 row affected (18.67 sec)

独占写锁

对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其他进程的读写操作。在锁释放前不能写其他表。

Transaction-A
mysql> set autocommit=0;
Query OK, 0 rows affected (0.05 sec) mysql> lock table myisam_lock write;
Query OK, 0 rows affected (0.03 sec) mysql> update myisam_lock set v='2001' where k='2';
Query OK, 1 row affected (0.00 sec) mysql> select * from myisam_lock;
9 rows in set (0.00 sec) mysql> update innodb_lock set v='1001' where k='1';
ERROR 1100 (HY000): Table 'innodb_lock' was not locked with LOCK TABLES mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
Transaction-B
mysql> select * from myisam_lock;
9 rows in set (42.83 sec)

总结:表锁,读锁会阻塞写,不会阻塞读。而写锁则会把读写都阻塞

查看加锁情况

show open tables; 1表示加锁,0表示未加锁。

mysql> show open tables where in_use > 0;
+----------+-------------+--------+-------------+
| Database | Table | In_use | Name_locked |
+----------+-------------+--------+-------------+
| lock | myisam_lock | 1 | 0 |
+----------+-------------+--------+-------------+

分析表锁定

可以通过检查table_locks_waited 和 table_locks_immediate 状态变量分析系统上的表锁定:show status like 'table_locks%'

mysql> show status like 'table_locks%';
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Table_locks_immediate | 104 |
| Table_locks_waited | 0 |
+----------------------------+-------+

table_locks_immediate: 表示立即释放表锁数。

table_locks_waited: 表示需要等待的表锁数。此值越高则说明存在着越严重的表级锁争用情况。

此外,MyISAM的读写锁调度是写优先,这也是MyISAM不适合做写为主表的存储引擎。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永久阻塞。

什么场景下用表锁

InnoDB默认采用行锁,在未使用索引字段查询时升级为表锁。MySQL这样设计并不是给你挖坑。它有自己的设计目的。

即便你在条件中使用了索引字段,MySQL会根据自身的执行计划,考虑是否使用索引(所以explain命令中会有possible_key 和 key)。如果MySQL认为全表扫描效率更高,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。

第一种情况:全表更新。事务需要更新大部分或全部数据,且表又比较大。若使用行锁,会导致事务执行效率低,从而可能造成其他事务长时间锁等待和更多的锁冲突。

第二种情况:多表级联。事务涉及多个表,比较复杂的关联查询,很可能引起死锁,造成大量事务回滚。这种情况若能一次性锁定事务涉及的表,从而可以避免死锁、减少数据库因事务回滚带来的开销。

页锁

开销和加锁时间介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发处理能力一般。只需了解一下。

总结

1 InnoDB 支持表锁和行锁,使用索引作为检索条件修改数据时采用行锁,否则采用表锁。

2 InnoDB 自动给修改操作加锁,给查询操作不自动加锁

3 行锁可能因为未使用索引而升级为表锁,所以除了检查索引是否创建的同时,也需要通过explain执行计划查询索引是否被实际使用。

4 行锁相对于表锁来说,优势在于高并发场景下表现更突出,毕竟锁的粒度小。

5 当表的大部分数据需要被修改,或者是多表复杂关联查询时,建议使用表锁优于行锁。

6 为了保证数据的一致完整性,任何一个数据库都存在锁定机制。锁定机制的优劣直接影响到一个数据库的并发处理能力和性能。

到这里,Mysql的表锁和行锁机制就介绍完了,若你不清楚InnoDB的行锁会升级为表锁,那以后会吃大亏的。若有打什么不对的地方请指正。若觉得文章不错,麻烦点个赞!来都来了,留下你的痕迹吧!

MySQL 行锁 表锁机制的更多相关文章

  1. MySQL锁(表锁,行锁,共享锁,排它锁,间隙锁)使用详解

    锁,在现实生活中是为我们想要隐藏于外界所使用的一种工具.在计算机中,是协调多个进程或县城并发访问某一资源的一种机制.在数据库当中,除了传统的计算资源(CPU.RAM.I/O等等)的争用之外,数据也是一 ...

  2. mysql 行锁 表锁

    MySQL数据库 - 引擎: - innodb - 支持事务 - 锁 - 行锁 - 表锁 - 示例: - 终端: begin; select xx from xx for update; commit ...

  3. MYSQL进阶学习笔记八:MySQL MyISAM的表锁!(视频序号:进阶_18-20)

    知识点九:MySQL MyISAM表锁(共享读锁)(18) 为什么会有锁: 打个比方,我们到淘宝买一件商品,商品只有一件库存,这时候如果还有另外一个人也在买,那么如何解决是你买到还是另一个人买到的问题 ...

  4. 【MySQL 读书笔记】全局锁 | 表锁 | 行锁

    全局锁 全局锁是针对数据库实例的直接加锁,MySQL 提供了一个加全局锁的方法, Flush tables with read lock 可以使用锁将整个表的增删改操作都锁上其中包括 ddl 语句,只 ...

  5. 浅谈SQL Transaction在请求中断后的行锁表锁

    最近在维护Web Service接口时,由于数据数据量达到千万级别,接口调用不时出现错误让人不胜烦恼,经过性能测试查出瓶颈在数据库数据处理上,可着实忙了一番.相信众多程序猿和DBA都会头痛性能的问题, ...

  6. 商品库存“存取设计”,MySQL事务、表锁、行锁

    MySQL  使用 SELECT ... FOR UPDATE 做事务写入前的确认 以MySQL 的InnoDB 为例,预设的 Tansaction isolation level 为 REPEATA ...

  7. 数据库记录锁表锁实际研究笔记 --- MSSQLSERVER

    直切主题 现有一张表 table : ChenJi ID, DanWeiID,  Name,  ChenJi 表中记录 ID  DanWeiID    Name      ChenJi --- --- ...

  8. [转]MySQL 表锁和行锁机制

    本文转自:http://www.cnblogs.com/itdragon/p/8194622.html MySQL 表锁和行锁机制 行锁变表锁,是福还是坑?如果你不清楚MySQL加锁的原理,你会被它整 ...

  9. MySQL中的锁(表锁、行锁)

    锁是计算机协调多个进程或纯线程并发访问某一资源的机制.在数据库中,除传统的计算资源(CPU.RAM.I/O)的争用以外,数据也是一种供许多用户共享的资源.如何保证数据并发访问的一致性.有效性是所在有数 ...

随机推荐

  1. Unity3D中通过Animator动画状态机获取任意animation clip的准确播放持续时长

    Unity3d 4及之前的版本中动画的播放用的animation,可直接获取其播放持续长度.但5.x及以后的版本中都是用animator来播放动画了. https://docs.unity3d.com ...

  2. idea快速搭建springboot项目

    Spring Boot是由Pivotal团队提供的全新框架,设计目的是用来简化新Spring应用的初始搭建以及开发过程.它主要推崇的是'消灭配置',实现零配置. 那么,如何在idea中创建一个spri ...

  3. ADC/DAC设计常见40问

    本文章是关于ADC/DAC设计经典问答,涵盖时钟占空比.共模电压.增益误差.微分相位误差.互调失真等常见问题. 1. 什么是小信号带宽(SSBW)? 小信号带宽(Small Signal Bandwi ...

  4. mac cocos2dx android

    1. localhost:proj.android mxhd4$ ./build_native.sh 报错 Compile++ thumb  : cocosdenshion_static <= ...

  5. OR1200中指令Cache的结构

    下面内容摘自<步步惊芯--软核处理器内部设计分析>一书 12.3 ICache结构 OR1200中实现ICache的文件有or1200_ic_top.v.or1200_ic_fsm.v.o ...

  6. Python 项目实践一(外星人入侵小游戏)第二篇

    接着上次的继续学习. 一 创建一个设置类 每次给游戏添加新功能时,通常也将引入一些新设置.下面来编写一个名为settings的模块,其中包含一个名为Settings的类,用于将所有设置存储在一个地方, ...

  7. 《跟我学IDEA》四、配置模板(提高代码编写效率)

    上一篇博文,我们学习了idea的一些实用配置,相信大家也对idea这个开发工具有了一个大概的了解.今天我们来学习模板的配置,idea提供很多模板从而提高编写代码的效率,比如说一些经常用的代码及生成文件 ...

  8. js 数组的常用方法归纳

    数组的常用方法归纳 slice(start,end) 传参:start代表从哪里开始截取,end代表截取结束的地方 var a = [1,2,3]a.slice(1);//[2,3] pop() 可以 ...

  9. 【java】Vector

    package com.tn.collect; import java.util.Enumeration; import java.util.Iterator; import java.util.Ve ...

  10. CET-4- translation1

    questions 2017/10/17 多年来,家长和老手都曾得到过这样一种信息(message):尽量利用任何机会表扬孩子,对他们所干的任何事情都要说好.据说这样做有助于提高孩子的自尊.但是近来许 ...