内容概述:

我们知道,数据也是一种供许多用户共享访问的资源。如何保证数据并发访问的一致性、有效性,是所有数据库必须解决的一个问题,锁的冲突也是影响数据库并发访问性能的一个重要因素。从这一角度来说,锁对于数据库而言就显得尤为重要。接下来我们了解各种锁的特征。

1.1 表锁

表级锁是mysql锁中粒度最大的一种锁,表示当前的操作对整张表加锁,资源开销比行锁少,不会出现死锁的情况,但是发生锁冲突的概率很大。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好地避免困扰我们的死锁问题。InnoDB支持表级锁。但是默认的行锁,而且只有在查询或者其他SQL语句通过索引才会使用行锁。

1.2 行锁

行锁的是mysql锁中粒度最小的一种锁,因为锁的粒度很小,所以发生资源争抢的概率也最小,并发性能最大,但是也会造成死锁,每次加锁和释放锁的开销也会变大。目前主要是Innodb使用行锁。根据使用方式也分为共享锁(S锁或者读锁)和排它锁(X锁或者写锁)。

1.3共享锁(读锁,S锁)

共享锁的具体逻辑如下案例:

若事务A对数据对象o加上S锁,则事务A可以读数据对象o但不能修改,其他事务只能再对数据对象o加S锁,而不能加X锁,直到事务A释放数据对象o上的S锁。这保证了其他事务可以读数据对象o,但在事务A释放数据对象o上的S锁之前不能对数据对象o做任何修改。

语法和案例如下:

# 加读锁

 Lock table tablename read;

#释放锁

unlock table;

案例如下 :

#session1 给表创建读锁(创建一个连接就是一个回话session)

mysql> lock table userinfo read;

Query OK, 0 rows affected (0.01 sec)

#可以读取当前表的数据

mysql> select * from userinfo ;

+----+----------+------+

| id | name     | age  |

+----+----------+------+

|  1 | zhangsan |   27 |

|  2 | lisi     |   27 |

|  3 | dd       | NULL |

|  4 | dd       |    2 |

+----+----------+------+

4 rows in set (0.00 sec)

#session1  因为给userinfo表加了读锁,所以不能读取其他表数据

mysql> select * from student_score;

ERROR 1100 (HY000): Table 'student_score' was not locked with LOCK TABLES

#session1  因为给表加的读锁,所以当前回话不能对表进行其他操作,

mysql> update userinfo set age=age+1;

ERROR 1099 (HY000): Table 'userinfo' was locked with a READ lock and can't be updated

#session2,在session1 没有释放锁之前,session2可以进行读取userinfo

mysql> select * from userinfo;

+----+----------+------+

| id | name     | age  |

+----+----------+------+

|  1 | zhangsan |   27 |

|  2 | lisi     |   27 |

|  3 | dd       | NULL |

|  4 | dd       |    2 |

+----+----------+------+

4 rows in set (0.00 sec)

#session2,在session1 没有释放锁之前,session2修改或删除表userinfo的任何数据都被阻塞

mysql> update  userinfo set age=1 where name='dd';

。。。。一直等待,直到seesion1 释放锁

#session1 释放锁,session1在执行下面释放锁语句后,session2才会提交上面修改语句。

mysql> unlock tables;

  

1.4 排它锁(写锁,X锁)

共享锁的具体逻辑如下案例:

若事务A对数据对象o加上X锁,事务A可以读数据对象o也可以修改数据对象o,其他事务不能再对数据对象o加任何锁,直到事务A释放数据对象o上的锁。这保证了其他事务在事务A释放数据对象o上的锁之前不能再读取和修改数据对象o。

# 加写锁给表

 Lock table tablename write;

# 加写锁给行

 select ... for update;

#释放表锁

unlock table;

案例如下 :

# session1 当前会话session1给表加写锁。

mysql> lock table userinfo write;

Query OK, 0 rows affected (0.00 sec)

#session1 当前会话可以修改此表数据

mysql> update userinfo set age=9;

Query OK, 4 rows affected (0.01 sec)

Rows matched: 4  Changed: 4  Warnings: 0

#session1  当前会话可以查询此表

mysql> select * from userinfo;

+----+----------+------+

| id | name     | age  |

+----+----------+------+

|  1 | zhangsan |    9 |

|  2 | lisi     |    9 |

|  3 | dd       |    9 |

|  4 | dd       |    9 |

+----+----------+------+

4 rows in set (0.00 sec)

#session2,在session1 没有释放锁之前,session2对表的任何数据任何操作(增删改查)userinfo的任何数据都被阻塞

mysql> update  userinfo set age=1 where name='dd';

。。。。一直等待,直到seesion1 释放锁

如上是锁表,接下来我们看看给行加锁的效果:

   #修改提交模式,改为手动提交

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

#session1 当前会话给id=1的行加写锁。

mysql>  select * from userinfo where id=1 for update;

+----+----------+------+

| id | name     | age  |

+----+----------+------+

|  1 | zhangsan |   10 |

+----+----------+------+

1 row in set (0.00 sec)

#session2 当前会话2 可以查询当前表数据。

mysql> select * from userinfo;

+----+----------+------+

| id | name     | age  |

+----+----------+------+

|  1 | zhangsan |   11 |

|  2 | lisi     |   12 |

|  3 | dd       |   12 |

|  4 | dd       |   11 |

+----+----------+------+

4 rows in set (0.00 sec)

#session2 当前会话2 可以操作其他行数据。

mysql> update userinfo set age=12 where id=2;

Query OK, 0 rows affected (0.01 sec)

Rows matched: 1  Changed: 0  Warnings: 0

#session2 当前会话2修改数据涉及到id=1的行,所以被阻塞知道行锁被释放

mysql> update userinfo set age=12 ;

。。。。一直被阻塞,直到sesssion1 执行commit语句。

#session1 回话1 提交数据,也就是释放锁

mysql> commit;

Query OK, 0 rows affected (0.00 sec)

  

1.5意向锁

意向共享锁和意向排它锁总称为意向锁。意向锁的出现是为了支持Innodb支持多粒度锁。它是表级别的锁。两种锁含义如下:

1.意向共享锁:事务想要在获得表中某些记录的共享锁(读锁),需要在表上先加意向共享锁。

2.意向互斥锁:事务想要在获得表中某些记录的互斥锁(写锁),需要在表上先加意向互斥锁。

当我们需要给一个加表锁的时候,我们需要根据意向锁去判断表中有没有数据行被锁定,以确定是否能加成功。如果意向锁是行锁,那么我们就得遍历表中所有数据行来判断。如果意向锁是表锁,则我们直接判断一次就知道表中是否有数据行被锁定了。所以说将意向锁设置成表级别的锁的性能比行锁高的多。

所以一项所的作用就是当一个事务在需要获取资源的锁定时,如果该资源已经被排他锁占用,则数据库会自动给该事务申请一个该表的意向锁。如果自己需要一个共享锁定,就申请一个意向共享锁。如果需要的是某行(或者某些行)的排他锁定,则申请一个意向排他锁。

业务执行流程如下:

有了意向锁之后,事务A在申请行锁(写锁)之前,数据库会自动先给事务A申请表的意向排他锁。当事务B去申请表的写锁时就会失败,因为表上有意向排他锁之后事务B申请表的写锁时会被阻塞。明显提高性能。快速判断好此表有被锁的数据,所以,不能获取到此表的表级别的锁。

1.6 乐观锁

乐观锁不是数据库自带的,是需要我们自己写业务去实现。乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。

操作流程如下:

在表中的数据进行更新操作时,先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。这样做的好处是避免了长事务中的数据库加锁开销,大大提升了大并发量下的系统整体性能表现。

伪业务代码如下:

#首先获取修改之前的数据和版本号

SELECT data AS old_data, version AS old_version FROM …;

#进行修改,条件是当前数据的版本号和之前的老版本号一致,表示没有人进行过修改

Int updaterow= UPDATE SET data = new_data, version = new_version WHERE version = old_version

// 表示修改成功,拿到了乐观锁

if (updated row > 0) {

// 乐观锁获取成功,操作完成

} else {

// 乐观锁获取失败,回滚并重试

}

  

1.7 悲观锁

悲观锁指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)

案例如下:

#关闭自动提交后

set autocommit=0; 

#开始事务

 start transaction;

#查询当前商品信息,然后通过for update锁定数据防止其他事务修改

select status  from goods where goodsid=1 for update;

#根据商品信息生成订单

insert into orders (id,goodsid) values (null,1);

#修改商品status为2

update t_goods set status=2;

#执行完毕,提交事务

commit; 

  

上述案例就实现了悲观锁,悲观锁就是悲观主义者,它会认为我们在事务A中操作数据1的时候,一定会有事务B来修改数据1,所以,在第2步我们将数据查询出来后直接加上排它锁(X)锁,防止别的事务来修改事务1,直到我们commit后,才释放了排它锁。

需要注意的是,乐观锁保证了数据处理时的安全性但是更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入居多,对吞吐要求不高,可使用悲观锁。而且要注意的是悲观锁加锁造成了开销增加,降低了性能,而且还会出现死锁。

1.8 间歇锁

间隙锁(Gap Lock)是Innodb在提交下为了解决幻读问题时引入的锁机制,(下面的所有案例没有特意强调都使用可重复读隔离级别)幻读的问题存在是因为新增或者更新操作,这时如果进行范围查询的时候(加锁查询),会出现不一致的问题,这时使用不同的行锁已经没有办法满足要求,需要对一定范围内的数据进行加锁,间隙锁就是解决这类问题的。在可重复读隔离级别下,数据库是通过行锁和间隙锁共同组成的(next-key lock),来实现的。加锁规则有以下特性:

1.加锁的基本单位是(next-key lock),他是前开后闭原则

2.插叙过程中访问的对象会增加锁

3.索引上的等值查询--给唯一索引加锁的时候,next-key lock升级为行锁

4.索引上的等值查询--向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁

5.唯一索引上的范围查询会访问到不满足条件的第一个值为止。

id(主键)

name

age

5

name1

18

10

name2

19

15

name3

20

20

name4

21

25

name5

22

以上数据为了解决幻读问题,更新的时候不只是对上述的五条数据增加行锁,还对于中间的取值范围增加了6间隙锁,(-∞,5](5,10](10,15](15,20](20,25](25,+∞]

如下案例:

#session1 操作会话session1 开启事务

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

#session1 更新id=11的数据,获取行锁。

mysql> select * from userinfo where id=11  for update;

Empty set (0.00 sec)

#session2 操作会话session2 开启事务

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

#session2 在会话session2 新增数据主键为12

mysql> insert into userinfo values(12,'hh',18);

.... 一直处于阻塞状态

#如果等待时间过长,session1没有释放锁,会抛出如下异常。

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

  

如上事务session1和事务session2时,事务session1会对数据库表增加(10,15]这个区间锁,这时insert id = 12 的数据的时候就会因为区间锁(10,15]而被锁住无法执行。

特别注意,间歇锁,如果操作不当会出现死锁。如下:

事务A

事务B

begin;
select * from t where id = 9 for update;

begin;
select * from t where id = 6 for update;

insert into user value(7,7,7)
一直阻塞

insert into user value(7,7,7)
一直阻塞

如上,事务A获取到(5,10]之间的间隙锁不允许其他的DDL操作,在事务提交,间隙锁释放之前,事务B也获取到了间隙锁(5,10],这时两个事务就处于死锁状态

1.9记录锁

记录锁,它封锁索引记录,作用于唯一索引上。如果执行这条语句(select * from userinfo where id=4 for update;)它会在id=4的索引记录上加锁,以阻止其他事务插入,更新,删除id=4的这一行。就是说,当一个会话执行这条语句之后,其它会话,执行下面这几种语句,都会处于阻塞状态。

select * from userinfo where id=1 for update;

delete from userinfo where id=4;

insert into userinfo values(4,'hh',18);

阻塞时间过长可能出抛出如下错误:

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

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

1.10临键锁

临键锁,作用于非唯一索引上,是记录锁与间隙锁的组合,如下图表格。

id(主键)

name

age

5

name1

18

10

name2

19

15

name3

20

20

name4

21

25

name5

22

它的封锁范围,既包含索引记录,又包含索引之前的区间,即(-∞,5](5,10](10,15](15,20](20,25](25,+∞]。

如下在事务中执行。

Update userinfo SET age=19 where id= 10;

Select * from userinfo where id=10 FOR UPDATE;

  

这两个语句都会锁定(5,10],(10,15)这两个区间。即, InnoDB 会获取该记录行的 临键锁 ,并同时获取该记录行下一个区间的间隙锁。

1.11 死锁

死锁是指两个或两个以上事务在执行过程中因争抢锁资源而造成的互相等待的现象。如下图7-3所示的操作流程。

上图所示,这两种情况即为死锁产生的常规情景。事务A等着事务B释放锁,事务B等着事务A释放锁。就会出现相互等待,一直等待下去。避免这种问题的办法有两种,一种是,等待事务超时,主动回滚。第二种是进行死锁检查,主动回滚某条事务,让别的事务能继续走下去。可以使用相关指令查询当前查看正在被锁的事务。

#查看正在被锁的事务

SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

#可以通过如下指令,杀死当前事务进程,释放锁。 trx_id字段对应上面语句结果对应的第一个字段值

kill trx_id;

  

一小时搞懂Mysql锁机制的更多相关文章

  1. 程序员必须了解的知识点——你搞懂mysql索引机制了吗?

    一.索引是什么 MySQL官方对索引的定义为:索引(Index)是帮助MySQL 高效 获取数据的数据结构,而MYSQL使用的数据结构是:B+树 在这里推荐大家看一本书,<深入理解计算机系统的书 ...

  2. 一文搞懂Flink Window机制

    Windows是处理无线数据流的核心,它将流分割成有限大小的桶(buckets),并在其上执行各种计算. 窗口化的Flink程序的结构通常如下,有分组流(keyed streams)和无分组流(non ...

  3. Mysql锁机制介绍

    Mysql锁机制介绍 一.概况MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制.比如,MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking ...

  4. Mysql锁机制--索引失效导致行锁变表锁

    Mysql 系列文章主页 =============== Tips:在阅读本文前,最好先阅读 这篇(Mysql锁机制--行锁)文章~ 在上篇文章中,我们看到InnoDB默认的行锁可以使得操作不同行时不 ...

  5. mysql锁机制详解

    前言 大概几个月之前项目中用到事务,需要保证数据的强一致性,期间也用到了mysql的锁,但当时对mysql的锁机制只是管中窥豹,所以本文打算总结一下mysql的锁机制. 本文主要论述关于mysql锁机 ...

  6. 对mysql锁机制的学习

    1.对于mysql学习,经常翻看一些博客,论坛,好像或多或少有mysq锁机制的学习与总结,所以今天有必要 对mysql锁机制的一些个人的总结,以便以后深入的学习. 2.学习这件事,从来都是“深入浅出” ...

  7. mysql锁机制(转载)

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

  8. 再谈mysql锁机制及原理—锁的诠释

    加锁是实现数据库并发控制的一个非常重要的技术.当事务在对某个数据对象进行操作前,先向系统发出请求,对其加锁.加锁后事务就对该数据对象有了一定的控制,在该事务释放锁之前,其他的事务不能对此数据对象进行更 ...

  9. mysql锁机制和事务隔离

    mysql事务 1.InnoDB事务原理 事务(Transaction)是数据库区别于文件系统的重要特性之一,事务会把数据库从一种一致性状态转换为另一种一致性状态. 在数据库提交时,可以确保要么所有修 ...

随机推荐

  1. nosql数据库之Redis集群

    Redis 集群是一个可以在多个 Redis 节点之间进行数据共享的设施(installation). Redis 集群不支持那些需要同时处理多个键的 Redis 命令, 因为执行这些命令需要在多个 ...

  2. Docker的镜像理解以及容器的备份、恢复和迁移操作

    Docker的镜像理解以及容器的备份.恢复和迁移操作 这篇文章主要介绍了Docker的镜像理解以及容器的备份.恢复和迁移操作,覆盖很多原理知识以及容器的重要操作步骤,极力推荐!需要的朋友可以参考下 D ...

  3. 『动善时』JMeter基础 — 28、JMeter函数助手详解

    目录 1.函数助手介绍 2.函数助手中的函数分类 3.常用函数介绍 (1)__Random函数 (2)__counter函数 (3)__time函数 (4)__property函数 (5)__setP ...

  4. Systemverilog MCDF寄存器描述

    前三个寄存器是读写寄存器(控制寄存器) (一)地址0x00 :32bit bit[0]通道使能,1打开,0关闭.复位1. bit[2:1]优先级,0最高 bit[5:3]数据包长度,是解码对应的. 0 ...

  5. 限流&熔断的考量

    限流的原则,是尽量在流量源头限,并且是需要依据现有团队所掌握的技能来. 如上最左侧便是主要流量的来源入口,首先就要限制的地方就是slb节点的income流量 slb节点的流量特点是啥?加限流怎么加?限 ...

  6. Go语言实现Snowflake雪花算法

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com/archives/527 每次放长假的在家里的时候,总想找点简单的例子来看看实现原理,这 ...

  7. jupyter notebook 默认文件路径修改以及启动

    其实这个方法有时候不是特别有效额 方法一: 查了网上好多其他的方法,但是都没用,只好独辟蹊径了. 首先找到anaconda的安装路径,找到jupyter notebook,我的是如下: 发送快捷方式到 ...

  8. javascript获取日期,年月,日

    <SCRIPT LANGUAGE="JavaScript"> var myDate = new Date();     myDate.getYear();       ...

  9. .net core 使用阿里云分布式日志

    前言 好久没有出来夸白了,今天教大家简单的使用阿里云分布式日志,来存储日志,没有阿里云账号的,可以免费注册一个 开通阿里云分布式日志(有一定的免费额度,个人测试学习完全没问题的,香) 阿里云日志地址: ...

  10. 关于switch语句的使用方法---正在苦学java代码的新手之菜鸟日记

    输入月份与年份,判断所输入的月份有多少天. switch支持和不支持的类型 支持的类型 int 类型 short 类型 byte 类型 char 类型 enum (枚举)类型 (java5.0 之后支 ...