读锁与写锁

  • 读锁:共享锁、Shared Locks、S锁。
  • 写锁:排他锁、Exclusive Locks、X锁。
读锁:只能读不允许写
写锁:不能读也不能写,只允许自己写
但是允许其他事务进行普通的select操作

读操作

对于普通 SELECT 语句,InnoDB 不会加任何锁

select … lock in share mode

将查找到的数据加上一个S锁,允许其他事务继续获取这些记录的S锁,不能获取这些记录的X锁(会阻塞)

使用场景:读出数据后,其他事务不能修改,但是自己也不一定能修改,因为其他事务也可以使用select … lock in share mode 继续加读锁。

select … for update

将查找到的数据加上一个X锁,不允许其他事务获取这些记录的S锁和X锁。

使用场景:读出数据后,其他事务即不能写,也不能加读锁,那么就导致只有自己可以修改数据

写操作

  • DELETE:删除一条数据时,先对记录加X锁,再执行删除操作。
  • INSERT:插入一条记录时,会先加隐式锁来保护这条新插入的记录在本事务提交前不被别的事务访问到。
  • UPDATE
    • 如果被更新的列,修改前后没有导致存储空间变化,那么会先给记录加X锁,再直接对记录进行修改。
    • 如果被更新的列,修改前后导致存储空间发生了变化,那么会先给记录加X锁,然后将记录删掉,再Insert一条新记录。
隐式锁:一个事务插入一条记录后,还未提交,这条记录会保存本次事务id,而其他事务如果想来读取这个记录会发现事务id不对应,所以相当于在插入一条记录时,隐式的给这条记录加了一把隐式锁。

行锁与表锁

行锁:

  • LOCK_REC_NOT_GAP:单个行记录上的锁。
  • LOCK_GAP:间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。
  • LOCK_ORDINARY:锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。

间隙锁(LOCK_GAP、GAP)

一、READ COMMITTED隔离级别下:

1)查询使用是的主键的情况

事务一(session1)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1 where a = 1 for update;
+---+------+------+------+------+
| a | b | c | d | e |
+---+------+------+------+------+
| 1 | 1 | 1 | 1 | 1 |
+---+------+------+------+------+
1 row in set (0.00 sec)

事务二(session2)

注意是在session1未进行事务commit的情况下

mysql> select * from t1 where a = 1 for update; -- 会阻塞

mysql> select * from t1 where a = 2 for update; -- 不会阻塞

总结:查询使用的是主键时,只需要在主键值对应的那一个条数据加锁即可。

2)查询使用的是唯一索引

事务一(session1)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1 where b = 1 for update;
+---+------+------+------+------+
| a | b | c | d | e |
+---+------+------+------+------+
| 1 | 1 | 1 | 1 | 1 |
+---+------+------+------+------+
1 row in set (0.00 sec)

事务二(session2)

mysql> select * from t1 where b = 1 for update; -- 会阻塞

mysql> select * from t1 where b = 2 for update; -- 不会阻塞

总结:查询使用的是唯一索引时,只需要对查询值所对应的唯一索引记录项和对应的聚集索引上的项加锁即可。

3)查询使用的是普通索引

事务一(session1)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1 where e = '6' for update;
+----+------+------+------+------+
| a | b | c | d | e |
+----+------+------+------+------+
| 6 | 6 | 1 | 4 | 6 |
| 12 | 12 | 1 | 1 | 6 |
+----+------+------+------+------+
2 rows in set (0.00 sec)

事务二(session2)

mysql> select * from t1 where a = 6 for update; -- 会阻塞
mysql> select * from t1 where a = 12 for update; -- 会阻塞
mysql> select * from t1 where a = 1 for update; -- 不会阻塞
mysql> select * from t1 where a = 2 for update; -- 不会阻塞
mysql> insert t1(b,c,d,e) values(20,1,1,'51'); -- 不会阻塞
mysql> insert t1(b,c,d,e) values(21,1,1,'61'); -- 不会阻塞

总结:查询使用的是普通索引时,会对满足条件的索引记录都加上锁,同时对这些索引记录对应的聚集索引上的项也加锁。

4)查询时没有使用到索引

事务一(session1)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1 where c = '1' for update;
+----+------+------+------+------+
| a | b | c | d | e |
+----+------+------+------+------+
| 1 | 1 | 1 | 1 | 1 |
| 2 | 2 | 1 | 2 | 2 |
| 4 | 3 | 1 | 1 | 4 |
| 6 | 6 | 1 | 4 | 6 |
| 8 | 8 | 1 | 8 | 8 |
| 10 | 10 | 1 | 2 | 10 |
| 12 | 12 | 1 | 1 | 6 |
+----+------+------+------+------+
7 rows in set (0.00 sec)

事务二(session2)

mysql> select * from t1 where a = 1 for update; -- 会阻塞

mysql> select * from t1 where a = 2 for update; -- 会阻塞

mysql> select * from t1 where a = 3 for update; -- 不会阻塞

mysql> select * from t1 where a = 7 for update; -- 不会阻塞

总结:查询的时候没有走索引,也只会对满足条件的记录加锁。

二、REPEATABLE READ隔离级别下:

1)查询使用是的主键的情况

  与READ COMMITTED隔离级别一样

2)查询使用的是唯一索引

  与READ COMMITTED隔离级别一样

3)查询使用的是普通索引

事务一(session1)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1 where e = '6' for update;
+----+------+------+------+------+
| a | b | c | d | e |
+----+------+------+------+------+
| 6 | 6 | 1 | 4 | 6 |
| 12 | 12 | 1 | 1 | 6 |
+----+------+------+------+------+
2 rows in set (0.00 sec)

事务二(session2)

mysql> select * from t1 where a = 6 for update; -- 会阻塞
mysql> select * from t1 where a = 12 for update; -- 会阻塞
mysql> select * from t1 where a = 1 for update; -- 不会阻塞
mysql> select * from t1 where a = 2 for update; -- 不会阻塞
mysql> insert t1(b,c,d,e) values(20,1,1,'51'); -- 会阻塞
mysql> insert t1(b,c,d,e) values(21,1,1,'61'); -- 会阻塞

总结:REPEATABLE READ级别可以解决幻读,解决的方式就是加了GAP锁。

4)查询时没有使用到索引

事务一(session1)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1 where c = '1' for update;
+----+------+------+------+------+
| a | b | c | d | e |
+----+------+------+------+------+
| 1 | 1 | 1 | 1 | 1 |
| 2 | 2 | 1 | 2 | 2 |
| 4 | 3 | 1 | 1 | 4 |
| 6 | 6 | 1 | 4 | 6 |
| 8 | 8 | 1 | 8 | 8 |
| 10 | 10 | 1 | 2 | 10 |
| 12 | 12 | 1 | 1 | 6 |
+----+------+------+------+------+
7 rows in set (0.00 sec)

事务二(session2)

mysql> select * from t1 where a = 1 for update; -- 会阻塞
mysql> select * from t1 where a = 2 for update; -- 会阻塞
mysql> select * from t1 where a = 3 for update; -- 不会阻塞
mysql> select * from t1 where a = 7 for update; -- 会阻塞(在READ COMMITTED级别中不会阻塞,跟解决幻读有关系)

总结:查询的时候没有走索引,会对表中所有的记录以及间隙加锁。

表锁

1、表级别的S锁、X锁

在对某个表执行SELECT、INSERT、DELETE、UPDATE语句时,InnoDB存储引擎是不会为这个表添加表级别的S锁或者X锁的。

在对某个表执行ALTER TABLE、DROP TABLE这些DDL语句时,其他事务对这个表执行SELECT、INSERT、DELETE、UPDATE的语句会发生阻塞,或者,某个事务对某个表执行SELECT、INSERT、DELETE、UPDATE语句时,其他事务对这个表执行DDL语句也会发生阻塞。这个过程是通过使用的元数据锁(英文名:Metadata Locks,简称MDL)来实现的,并不是使用的表级别的S锁和X锁。

  • LOCK TABLES t1 READ:对表t1加表级别的S锁。
  • LOCK TABLES t1 WRITE:对表t1加表级别的X锁。

尽量不用这两种方式去加锁,因为InnoDB的优点就是行锁,所以尽量使用行锁,性能更高。

2、IS锁、IX锁

  • IS锁:意向共享锁、Intention Shared Lock。当事务准备在某条记录上加S锁时,需要先在表级别加一个IS锁。
  • IX锁,意向排他锁、Intention Exclusive Lock。当事务准备在某条记录上加X锁时,需要先在表级别加一个IX锁。

IS、IX锁是表级锁,它们的提出仅仅为了在之后加表级别的S锁和X锁时可以快速判断表中的记录是否被上锁,以避免用遍历的方式来查看表中有没有上锁的记录。

3、AUTO-INC锁

  • 在执行插入语句时就在表级别加一个AUTO-INC锁,然后为每条待插入记录的AUTO_INCREMENT修饰的列分配递增的值,在该语句执行结束后,再把AUTO-INC锁释放掉。这样一个事务在持有AUTO-INC锁的过程中,其他事务的插入语句都要被阻塞,可以保证一个语句中分配的递增值是连续的。
  • 采用一个轻量级的锁,在为插入语句生成AUTO_INCREMENT修饰的列的值时获取一下这个轻量级锁,然后生成本次插入语句需要用到的AUTO_INCREMENT列的值之后,就把该轻量级锁释放掉,并不需要等到整个插入语句执行完才释放锁。

4、系统变量innodb_autoinc_lock_mode:

  • innodb_autoinc_lock_mode值为0:采用AUTO-INC锁。
  • innodb_autoinc_lock_mode值为2:采用轻量级锁。
  • 当innodb_autoinc_lock_mode值为1:当插入记录数不确定是采用AUTO-INC锁,当插入记录数确定时采用轻量级锁。

悲观锁

悲观锁用的就是数据库的行锁,认为数据库会发生并发冲突,直接上来就把数据锁住,其他事务不能修改,直至提交了当前事务。

顾名思义就是不管什么情况都直接给上锁,性能不是很友好

乐观锁

乐观锁其实是一种思想,认为不会锁定的情况下去更新数据,如果发现不对劲,才不更新(回滚)。在数据库中往往添加一个version字段来实现。

往往添加一个额外字段来控制,比如在update中来根据version字段来判断是否修改

死锁

事务一(session1)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1 where a =1 for update; -- 1
+---+------+------+------+------+
| a | b | c | d | e |
+---+------+------+------+------+
| 1 | 1 | 1 | 1 | 1 |
+---+------+------+------+------+
1 row in set (0.00 sec)
mysql> update t1 set c = 2 where a = 4; -- 3,一开始会阻塞
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

事务二(session2)

mysql> begin;
Query OK, 0 rows affected (0.00 sec) mysql> delete from t1 where a = 4; -- 2
Query OK, 1 row affected (0.00 sec) mysql> delete fromt t1 where a = 1; -- 4,按道理会阻塞,并产生死锁,但是mysql有死锁检查机制让死锁中断 ERROR 1064 (42000): You have an error in your SQL syntax; check the manual thatcorresponds to your MySQL server version for the right syntax to use near 't1 where a= 1' at line 1 mysql> delete from t1 where a = 1;
Query OK, 1 row affected (0.01 sec)

死锁检测

  • 系统变量innodb_deadlock_detect:控制是否打开死锁检测,默认打开。
  • 系统变量innodb_lock_wait_timeout:等待锁的超时时间,默认50s。
  • 系统变量innodb_print_all_deadlocks:将所有的死锁日志写入到mysql的错误日志中,默认是关闭的。检测到死锁时,InnoDB会在导致死锁的事务中选择一个权重比较小的事务来回滚,这个权重值可能是由该事务影响的行数(增加、删除、修改)决定的。
SHOW ENGINE INNODB STATUS;   ----看看最近死锁的日志

避免死锁

  • 以固定的顺序访问表和行
  • 大事务拆小,大事务更容易产生死锁
  • 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率
  • 降低隔离级别(最好不要轻易改动,若业务需要则采纳此条建议)
  • 为表添加合理的索引

Mysql之锁(一)的更多相关文章

  1. mysql乐观锁总结和实践--转

    原文地址:http://chenzhou123520.iteye.com/blog/1863407 上一篇文章<MySQL悲观锁总结和实践>谈到了MySQL悲观锁,但是悲观锁并不是适用于任 ...

  2. 【MySQL】锁入门

    要做的完全掌握MySQL/InnoDB的加锁规则,甚至是其他任何数据库的加锁规则,需要具备以下的一些知识点 了解数据库的一些基本理论知识:数据的存储格式 (堆组织表 vs 聚簇索引表):并发控制协议 ...

  3. mysql行锁和表锁

    mysql innodb支持行锁和表锁,但是MyIsam只支持表锁.现在我们说说mysql innodb的行锁和 有如下表id为主键 为了出现演示效果,我们将mysql的autocommit设置为0 ...

  4. MYSQL的锁介绍,以及死锁发生情况-带例子

    mysql锁能在并发情况下的mysql进行更好的优化 MySQL有三种锁的级别:页级.表级.行级,这3种锁的特性可大致归纳如下: 表级锁:开销小,加锁快:不会出现死锁:锁定粒度大,发生锁冲突的概率最高 ...

  5. 《MySQL悲观锁总结和实践》乐观锁

    mysql乐观锁总结和实践 博客分类: MyBatis 数据库 mysql数据库乐观锁悲观锁 上一篇文章<MySQL悲观锁总结和实践>谈到了MySQL悲观锁,但是悲观锁并不是适用于任何场景 ...

  6. [转]关于MYSQL Innodb 锁行还是锁表

    关于mysql的锁行还是锁表,这个问题,今天算是有了一点头绪,mysql 中 innodb是锁行的,但是项目中居然出现了死锁,锁表的情况.为什么呢?先看一下这篇文章. 目时由于业务逻辑的需要,必须对数 ...

  7. mysql myisam 锁表问题<转>

    转自http://yafei001.iteye.com/blog/1841258 锁是计算机协调多个进程或线程并发访问某一资源的机制 .在数据库中,除传统的计算资源(如CPU.RAM.I/O等)的争用 ...

  8. mysql的锁--行锁,表锁,乐观锁,悲观锁

    一 引言--为什么mysql提供了锁 最近看到了mysql有行锁和表锁两个概念,越想越疑惑.为什么mysql要提供锁机制,而且这种机制不是一个摆设,还有很多人在用.在现代数据库里几乎有事务机制,aci ...

  9. MySQL- 锁机制及MyISAM表锁

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

  10. MySQL 行锁 表锁机制

    MySQL 表锁和行锁机制 行锁变表锁,是福还是坑?如果你不清楚MySQL加锁的原理,你会被它整的很惨!不知坑在何方?没事,我来给你们标记几个坑.遇到了可别乱踩.通过本章内容,带你学习MySQL的行锁 ...

随机推荐

  1. Dubbo源码剖析一之整体架构设计

    Dubbo基础二之架构及处理流程概述 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中进行Dubbo职能上的简单介绍,下面就其内部进行详细探究: 1.Dubbo调用关系 这个图是不是很熟 ...

  2. JMM之Java中锁概念的分类总结

    在Java的并发编程中不可避免的涉及到锁.从不同维护可以将锁进行不同的分类,如下: 1.乐观锁和悲观锁(根据读写操作的比例划分) 乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数 ...

  3. JMM之synchronized关键字

    对于通讯,涉及两个关键字volatile和synchronized: Java支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个变量的拷贝(虽然对象及其成员变量分配的内存实在共享 ...

  4. RSA公私钥生成与使用

    参考 KeyStore 简述 Keytool 简述 Certificate Chain (证书链) 简述 详解RSA加密算法

  5. pagehelper 自循环启动报错

    问题原因 问题产生的原因是 ServiceA实现类中引入了ServiceB,而在ServiceB实现类中又引入了ServiceA,导致循环依赖注入. 其实在代码开发过程中应该尽量避免这种操作的出现,即 ...

  6. intellij IDEA 安装、简单使用与创建javaWeb项目

    这里我用mac for intellij IDEA 进行演示 1.安装 安装的话不多说 与我之前发的webstorm一样  这里提供下intellij IDEA的破解站http://xidea.onl ...

  7. mysql 事务 隔离性 锁

    1.四大特性 1.1 原子性(Atomicity) 一个事务是不可分割的最小工作单位.一个事务是不可分割的最小工作单位. 利用undo log保证原子性,undo log记录的是操作的反向语句,例如执 ...

  8. Linux:保证数据安全落盘

    背景 在很多IO场景中,我们经常需要确保数据已经安全的写到磁盘上,以便在系统宕机重启之后还能读到这些数据.但是我们都知道,linux系统的IO路径还是很复杂的,分为很多层,每一层都可能会有buffer ...

  9. oj教程--队列

    队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表.进行插入操作的端称为队尾,进行删除操作的 ...

  10. selenium+python自动化102-登录页面滑动解锁(ActionChains)

    前言 登录页面会遇到滑动解锁,滑动解锁的目的就是为了防止别人用代码登录(也就是为了防止你自动化登录),有些滑动解锁是需要去拼图这种会难一点. 有些直接拖到最最右侧就可以了,本篇讲下使用 seleniu ...