Mysql基础(十九):锁

目录

1、Mysql锁机制

1.1、乐观锁和悲观锁

1.1.1、乐观锁

1.1.2、悲观锁

1.2、共享锁与排他锁

1.2.1、共享锁

1.2.2、排他锁

1.3、行锁与表锁

1.3.1、行锁

  1.3.2、表锁 

1.4、间隙锁

1.4.1、生活中的间隙锁

1.4.2、Mysql中的间隙锁

1.4.3、间隙是怎么划分的?

1.4.3、间隙锁锁定的区域

1.4.4、间隙锁的作用范围

1.4.5、next-key锁

2、MyISAM与InnoDB

3、总结


1、Mysql锁机制

MySQL死锁系列-常见加锁场景分析 - 孙龙-程序员 - 博客园

|--表级锁(锁定整个表)

|--页级锁(锁定一页)

|--行级锁(锁定一行)

|--共享锁(S锁,MyISAM 叫做读锁)

|--排他锁(X锁,MyISAM 叫做写锁)

|--悲观锁(抽象性,不真实存在这个锁)

|--乐观锁(抽象性,不真实存在这个锁)

对mysql乐观锁、悲观锁、共享锁、排它锁、行锁、表锁概念的理解_水中加点糖-CSDN博客

  • 在 Mysql 中,行级锁并不是直接锁记录,而是锁索引
  • 索引分为主键索引和非主键索引两种,如果一条sql 语句操作了主键索引,Mysql 就会锁定这条主键索引如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引
  • InnoDB 行锁是通过给索引项加锁实现的,如果没有索引,InnoDB 会通过隐藏的聚簇索引来对记录加锁。
  • 如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。因为没有了索引,找到某一条记录就得扫描全表,要扫描全表,就得锁定表。

1.1、乐观锁和悲观锁

乐观锁和悲观锁都是针对读(select)来说的。

1.1.1、乐观锁

  •  乐观锁不是数据库自带的,需要我们自己去实现。
  • 乐观锁是指 更新数据库时认为操作不会导致冲突,在操作数据时不加锁,而在进行更新后,再去判断是否有冲突了。

通常实现是这样的:在表中的数据进行更新时,先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。

  • 先查询出那条记录,获取出version字段。
    • 如果要对那条记录进行更新操作,则先判断此刻version的值是否与刚刚查询出来时的version的值相等。
    • 如果相等,则说明这段期间没有其他程序对其进行操作,则可以执行更新,将version字段的值加1。
    • 如果version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。

举例:下单操作包括3步骤:

  • 1、查询出商品信息

        select (status,status,version) from t_goods where id=#{id}

  • 2、根据商品信息生成订单
  • 3、修改商品status为2

update t_goods set status=2,version=version+1 where id=#{id} and version=#{version};

        除了自己手动实现乐观锁之外,现在网上许多框架已经封装好了乐观锁的实现,如hibernate,需要时,可能自行搜索"hiberate 乐观锁"试试看。

1.1.2、悲观锁

        悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。

  • 悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。
  • 悲观锁涉及到的另外两个锁:就是共享锁与排它锁。
  • 共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴。

1.2、共享锁与排他锁

数据库的增删改操作默认都会加排他锁,而查询不会加任何锁

1.2.1、共享锁

        悲观锁一般数据库已经实现了,共享锁也属于悲观锁的一种,那么共享锁在mysql中是通过什么命令来调用呢。

在执行语句后面加上 lock in share mode就代表对某些资源加上共享锁了

select * from table where id=1 lock in share mode

1.2.2、排他锁

update、insert、delete 语句会自动加排它锁。

在执行的语句后面加上 for update

select * from table where id=1 for update

1.3、行锁与表锁

1.3.1、行锁

        mysql锁机制分为表级锁和行级锁。mysql行级锁包括共享锁与排他锁:

  • 共享锁又称为读锁,简称S锁
    • 共享锁:多个事务都可以加共享锁读同⼀⾏数据,但是别的事务不能写这⾏数据。
      • 一个事务获取了共享锁,其他事务也只能加共享锁或不加锁查询。
      • 其他事务不能写。
      • 其他事务加排他锁查不到,因为排他锁与共享锁不能存在同一数据上。  
  • 排他锁又称为写锁,简称X锁
    • 排他锁:⼀个事务可以读/写这⾏数据,别的事务只能读不能写。 
      • 一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁
      • 获取排他锁的事务可以对数据就行读取和修改
      • 其他事务可以通过select ...from...查询数据,因为普通查询没有任何锁机制。
      • 其他事务不能写。

        mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型。

  • 加排他锁可以使用select ...for update语句
  • 加共享锁可以使用select ... lock in share mode语句

  1.3.2、表锁 

 innodb的 表锁,分成意向共享锁 和意向拍他锁,表锁是innodb引擎⾃动加的,不⽤你⾃⼰去加。

  • 意向共享锁:加共享⾏锁时,必须先加共享表锁;
  • 意向排他锁 :给某⾏加排他锁时,必须先给表加排他锁。

mysql共享锁与排他锁 - java攻城狮 - 博客园

  •   insert、update、delete,innodb会⾃动给那⼀⾏加⾏级排他锁。
  •   select,innodb啥锁都不加。

 innodb从来不会⾃⼰主动加个共享锁的,除⾮你⽤下⾯的语句⾃⼰⼿动加个锁:

  • ⼿动加共享锁:select * from table where id=1 lock in share mode,那你就给那⼀⾏加了个共享锁,其他事务就不能来修改这⾏数据了。
  • ⼿动加排他锁:select * from table where id=1 for update,那你就给那⼀⾏加了个排他锁,意思就是你准备修改,别的事务就别修改了,别的事务的修改会hang住。这个要慎⽤,⼀般我们线上系统不⽤这个,容易搞出问题来。

      对⼀⾏数据,如果有⼈在修改会加个排他锁,然后你不能修改只能等着获取这把锁,但这时可以随便select,查询你的事务开始之前那⾏数据的某个版本。然后如果你修改某⾏数据,会同时拿这个表的排他锁,但是如果不同的事务修改不同的⾏,会拿不同⾏的⾏级排他锁,但⼤家都会拿⼀个表的排他锁,实际上 innodb 的表级排他锁可以随便拿,这个是没冲突的
        mysql innodb 存储引擎的默认锁模式,其实还挺不错的。相当于就是⼀⾏数据,同⼀个时刻只能⼀个⼈在修改,但是别⼈修改,你可以随便读,读是读某个版本的,⾛mvcc 机制

1.4、间隙锁

mySQL数据库间隙锁(mysql是如何解决幻读的)_sinat_27143551的博客-CSDN博客_mysql间隙锁解决幻读

MySQL 中锁的面试题总结 - IT酸菜鱼 - 博客园

其实innodb下的记录锁(也叫行锁),间隙锁,next-key锁统统属于排他锁。

  • Record Lock — 单个行记录上的锁;
  • Gap Lock — 间隙锁,锁定一个范围,不包括记录本身;
  • Next-Key Lock — 锁定一个范围,包括记录本身。

1.4.1、生活中的间隙锁

        编程的思想源于生活,生活中的例子能帮助我们更好的理解一些编程中的思想。生活中排队的场景,小明,小红,小花三个人依次站成一排,此时,如何让新来的小刚不能站在小红旁边,这时候只要将小红和她前面的小明之间的空隙封锁,将小红和她后面的小花之间的空隙封锁,那么小刚就不能站到小红的旁边。这里的小红,小明,小花,小刚就是数据库的一条条记录。
他们之间的空隙也就是间隙,而封锁他们之间距离的锁,叫做间隙锁。

1.4.2、Mysql中的间隙锁

        表中id为主键,number字段上有非唯一索引的二级索引,有什么方式可以让该表不能再插入number=5的记录?注意:此时按照二级索引建索引表,即插入时按照number顺序排序。

Mysql基础(十九):锁_第1张图片

         只要控制几个点,number=5之前不能插入记录,number=5现有的记录之间不能再插入新的记录,number=5之后不能插入新的记录,那么新的number=5的记录将不能被插入进来。

那么,mysql是如何控制number=5之前,之中,之后不能有新的记录插入呢(防止幻读)?
答案是用间隙锁,在RR级别下,mysql通过间隙锁可以实现锁定number=5之前的间隙,number=5记录之间的间隙,number=5之后的间隙,从而使的新的记录无法被插入进来

1.4.3、间隙是怎么划分的?

我们规定(id=A,number=B)代表一条字段id=A,字段number=B的记录,(C,D)代表一个区间,代表C-D这个区间范围。

图中根据number列,我们分为几个区间:(无穷小,2)(2,4)(4,5)(5,5)(5,11)(11,无穷大)。
只要这些区间对应的两个临界记录中间可以插入记录,就认为区间对应的记录之间有间隙。
例如:区间(2,4)分别对应的临界记录是(id=1,number=2),(id=3,number=4),这两条记录中间可以插入(id=2,number=3)等记录,那么就认为(id=1,number=2)与(id=3,number=4)之间存在间隙。

很多人会问,那记录(id=6,number=5)与(id=8,number=5)之间有间隙吗?
答案是有的,(id=6,number=5)与(id=8,number=5)之间可以插入记录(id=7,number=5),因此(id=6,number=5)与(id=8,number=5)之间有间隙的,

1.4.3、间隙锁锁定的区域

        根据检索条件向左寻找最靠近检索条件的记录值A,作为左区间,向右寻找最靠近检索条件的记录值B作为右区间,即锁定的间隙为(A,B)。
图一中,where number=5的话,那么间隙锁的区间范围为(4,11);

间隙锁的目的是为了防止幻读,其主要通过两个方面实现这个目的:

  • 防止间隙内有新数据被插入。
  • 防止已存在的数据,更新成间隙内的数据(例如防止numer=3的记录通过update变成number=5)

innodb自动使用间隙锁的条件:

  • 必须在RR级别下
  • 检索条件必须有索引(没有索引的话,mysql会全表扫描,那样会锁定整张表所有的记录,包括不存在的记录,此时其他事务不能修改不能删除不能添加)

1.4.4、间隙锁的作用范围

Mysql基础(十九):锁_第2张图片

案例一:

````
session 1:
start  transaction ;
select  * from news where number=4 for update ;

session 2:
start  transaction ;
insert into news value(2,4);#(阻塞)
insert into news value(2,2);#(阻塞)
insert into news value(4,4);#(阻塞)
insert into news value(4,5);#(阻塞)
insert into news value(7,5);#(执行成功)
insert into news value(9,5);#(执行成功)
insert into news value(11,5);#(执行成功)
````

        检索条件number=4,向左取得最靠近的值2作为左区间,向右取得最靠近的5作为右区间,因此,session 1的间隙锁的范围(2,4),(4,5),如下图所示:

Mysql基础(十九):锁_第3张图片

         间隙锁锁定的区间为(2,4)(4,5),即记录(id=1,number=2)和记录(id=3,number=4)之间间隙会被锁定,记录(id=3,number=4)和记录(id=6,number=5)之间间隙被锁定。

因此记录(id=2,number=4),(id=2,number=2),(id=4,number=4),(id=4,number=5)正好处在(id=3,number=4)和(id=6,number=5)之间,所以插入不了,需要等待锁的释放,而记录(id=7,number=5),(id=9,number=5),(id=11,number=5)不在上述锁定的范围内,因此都会插入成功。


案例二:

````
session 1:
start  transaction ;
select  * from news where number=13 for update ;

session 2:
start  transaction ;
insert into news value(11,5);#(执行成功)
insert into news value(12,11);#(执行成功)
insert into news value(14,11);#(阻塞)
insert into news value(15,12);#(阻塞)
update news set id=14 where number=11;#(阻塞)
update news set id=11 where number=11;#(执行成功)
````
检索条件number=13,向左取得最靠近的值11作为左区间,向右由于没有记录因此取得无穷大作为右区间,因此,session 1的间隙锁的范围(11,无穷大),如下图所示:

        

Mysql基础(十九):锁_第4张图片

         此表中没有number=13的记录的,innodb依然会为该记录左右两侧加间隙锁,间隙锁的范围(11,无穷大)。有人会问,为啥update news set id=14 where number=11会阻塞,但是update news set id=11 where number=11却执行成功呢?

        间隙锁采用在指定记录的前面和后面以及中间的间隙上加间隙锁的方式避免数据被插入

此图间隙锁锁定区域(11,无穷大),即记录(id=13,number=11)之后不能再插入记录,

update news set id=14 where number=11

这条语句如果执行的话,将会被插入到(id=13,number=11)的后面,也就是在区间(11,无穷大)之间,由于该区间被间隙锁锁定,所以只能阻塞等待,而

update news set id=11 where number=11

执行后是会被插入到(id=13,number=11)的记录前面,也就不在(11,无穷大)的范围内,所以无需等待,执行成功。


案例三:

````
session 1:
start  transaction ;
select  * from news where number=5 for update;

session 2:
start  transaction ;
insert into news value(4,4);#(阻塞)
insert into news value(4,5);#(阻塞)
insert into news value(5,5);#(阻塞)
insert into news value(7,11);#(阻塞)
insert into news value(9,12);#(执行成功)
insert into news value(12,11);#(阻塞)
update news set number=5 where id=1;#(阻塞)
update news set id=11 where number=11;#(阻塞)
update news set id=2 where number=4 ;#(执行成功)
update news set id=4 where number=4 ;#(阻塞)
````

        检索条件number=5,向左取得最靠近的值4作为左区间,向右取得11为右区间,因此,session 1的间隙锁的范围(4,5),(5,11),如下图所示:

Mysql基础(十九):锁_第5张图片

        有人会问,为啥insert into news value(9,12)会执行成功?间隙锁采用在指定记录的前面和后面以及中间的间隙上加间隙锁的方式避免数据被插入,(id=9,number=12)很明显在记录(13,11)的后面,因此不再锁定的间隙范围内。

为啥update news set number=5 where id=1会阻塞?
        number=5的记录的前面,后面包括中间都被封锁了,你这个update news set number=5 where id=1根本没法执行,因为innodb已经把你可以存放的位置都锁定了,因为只能等待。

同理,update news set id=11 where number=11

由于记录(id=10,number=5)与记录(id=13,number=11)中间的间隙被封锁了,你这句sql也没法执行,必须等待,因为存放的位置被封锁了。


案例四:

session 1:
start  transaction;
select * from news where number>4 for update;

session 2:
start  transaction;
update news set id=2 where number=4 ;#(执行成功)
update news set id=4 where number=4 ;#(阻塞)
update news set id=5 where number=5 ;#(阻塞)
insert into news value(2,3);#(执行成功)
insert into news value(null,13);#(阻塞)

检索条件number>4,向左取得最靠近的值4作为左区间,向右取无穷大,因此,session 1的间隙锁的范围(4,无穷大),如下图所示:

Mysql基础(十九):锁_第6张图片

 session2中之所以有些阻塞,有些执行成功,其实就是因为插入的区域被锁定,从而阻塞。


1.4.5、next-key锁

        next-key锁其实包含了记录锁和间隙锁,即锁定一个范围,并且锁定记录本身,InnoDB默认加锁方式是next-key 锁。
上面的案例一session 1中的sql是:

select * from news where number=4 for update ;

        next-key锁锁定的范围为间隙锁+记录锁,即区间(2,4)(4,5)加间隙锁,同时number=4的记录加记录锁。

链接:https://www.jianshu.com/p/bf862c37c4c9

2、MyISAM与InnoDB

https://blog.csdn.net/localhost01/article/details/78720727

  • MyISAM 操作数据都是使用的表锁,你更新一条记录就要锁整个表,导致性能较低,并发不高。当然同时它也不会存在死锁问题。
  • InnoDB 与 MyISAM 的最大不同有两点:
    • 一是 InnoDB 支持事务;
    • 二是 InnoDB 采用了行级锁。也就是你需要修改哪行,就可以只锁定哪行。

3、总结

对于以上,可以看得出来乐观锁和悲观锁的区别:

浅谈Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景_localhost01-CSDN博客_排他锁

  • 悲观锁实际使用了排他锁来实现(select **** for update)。文章开头说到,innodb加行锁的前提是:必须是通过索引条件来检索数据,否则会切换为表锁。
    • 因此,悲观锁在未通过索引条件检索数据时,会锁定整张表。导致其他程序不允许“加锁的查询操作”,影响吞吐。故如果在查询居多的情况下,推荐使用乐观锁。
    • “加锁的查询操作”:加过排他锁的数据行在其他事务中是不能修改的,也不能通过for update或lock in share mode的加锁方式查询,但可以直接通过select ...from...查询数据,因为普通查询没有任何锁机制。
  • 乐观锁更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入居多,对吞吐要求不高,可使用悲观锁。

也就是一句话:读用乐观锁,写用悲观锁。

你可能感兴趣的:(Mysql,mysql)