数据库(三):行锁和表锁,共享锁和排他锁,数据库引擎MyISAM和InnoDB,乐观锁和悲观锁


前言

之前我们提到了数据库的隔离性可能会出现的若干问题,以及数据库为了解决这些问题而提出来的若干种隔离级别。实际上,数据库底层实现这些隔离级别的原理本质上就是通过各种封锁。今天我们在这里总结一下两种封锁粒度。

零、数据库引擎

其实mysql中的引擎有很多种类,其中InnoDB和MyISAM引擎最常用

在mysql5.5版本前默认使用MyISAM引擎,之后使用InnoDB引擎

MyISAM 操作数据都是使用的表锁,你更新一条记录就要锁整个表,导致性能较低,并发不高。当然同时它也不会存在死锁问题。

而 InnoDB 与 MyISAM 的最大不同有两点:一是 InnoDB 支持事务;二是 InnoDB 采用了行级锁。

在 Mysql 中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql 语句操作了主键索引,Mysql 就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。

InnoDB 行锁是通过给索引项加锁实现的,如果没有索引,InnoDB 会通过隐藏的聚簇索引来对记录加锁。也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。因为没有了索引,找到某一条记录就得扫描全表,要扫描全表,就得锁定表。

一、封锁粒度

mysql中提供了两种封锁粒度:行级锁和表级锁。

我们在使用的过程中应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能性就越小,系统的并发程度就越好。

但是加锁需要消耗资源,锁的各种操作(包括获取锁,释放锁以及检查锁状态)都会增加系统开销。因此封锁粒度越小,系统开销越大。 在选择封锁粒度的时候需要在锁开销和并发程度之间做出一个权衡。

两者之间的区别如下:

  • 表级锁: 粒度最大 的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。但触发锁冲突的概率最高,并发度最低

  • 行级锁: 粒度最小 的一种锁,只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。并发度高,但加锁的开销也最大,加锁慢,会出现死锁。

其实除了可以分为表级锁和行级锁,锁还可以被划分为其他类型,具体如下图所示:

请添加图片描述
请添加图片描述

二、行锁

行锁就是一锁锁一行或者多行,mysql的行锁是基于索引家在的,所以行锁是要加载在索引响应的行上(比如当你更新表中的索引字段的时候会自动响应行锁)。当我们使用行锁锁定了数据库中某个表的某些记录的时候,当其他事务访问数据库中的该表,被锁定的记录不能被访问,其他的记录都可以访问到。

行锁的特点是锁冲突概率低,并发性高,但是会出现死锁的情况。

三、表锁

顾名思义,表锁就是一锁锁一整张表,在表被锁定的期间,其他事务不能对该表进行任何操作,必须等当前表的表锁被释放后才能进行操作。表锁响应的是非索引字段(比如你在更新表中的非索引字段的时候会自动调用表锁),即全表扫描,全表扫描时锁定整张表。

由于表锁每次都是锁一整张表,所以表锁的锁冲突几率特别高,表锁大大降低了出现死锁的情况

四、数据库中的属性锁

属性锁可以分为两类,共享锁和排他锁。

  • 排他锁(互斥锁),简写为X锁,又称之为写锁,简称X锁,当事务对数据加上写锁后,其他事务既不能对该数据添加读写,也不能对该数据添加写锁,写锁与其他锁都是互斥的。只有当前数据写锁被释放后,其他事务才能对其添加写锁或者是读锁。写锁主要是为了解决在修改数据时,不允许其他事务对当前数据进行修改和读取操作,从而可以有效避免”脏读”问题和“丢失修改”问题的产生。

  • 共享锁,简写为S锁,又称为读锁,简称S锁,当事务对数据加上读锁后,其他事务只能对该数据加读锁,不能做任何修改操作,也就是不能添加写锁。只有当数据上的读锁被释放后,其他事务才能对其添加写锁。共享锁主要是为了支持并发的读取数据而出现的,读取数据时,不允许其他事务对当前数据进行修改操作,从而避免”不可重读”的问题的出现。

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

当然,我们也可以手动使用共享锁和排他锁,如下列代码所示:


//共享锁
select * from 表名 lock in share mode
 
//排他锁
select * from 表名 for update

锁的兼容关系如下:
请添加图片描述

4.2. 意向锁

使用意向锁可以更容易地支持多粒度封锁。

在存在行级锁和表级锁的情况下,事务T如果想要对表A加上锁,就需要先检测是否有其他事务对表A或者表A中的任意一行加锁,如果一行一行去检查就显得非常沙雕且耗时,且效率极低,这个时候我们只需要检测意向锁是否被占用就行。

意向锁在原来的X/S锁的基础上引入了IX/IS,IX/IS都是表锁。有以下两个规定:

  • 一个事务在获得某一个数据行对象的S锁之前,必须先获得表的IS锁或者更强的锁
  • 一个事务在获得某一个数据行对象的X锁之前,必须首先获得表的IX锁。

通过引入意向锁,事务T想要对表A加X锁,只需要先检验是否有其他事务对表A加上了X/IX/S/IS锁,如果有就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。

各种锁的兼容关系如下:

请添加图片描述

五、乐观锁和悲观锁

  • 乐观锁
    乐观锁是相对悲观锁而言的,总是假设最好的情况,**每次拿数据的时候,都认为别人不会修改。但是在更新数据的时候,会判断再次期间有没有人去修改这个数据,如果发现被修改了即产生了冲突,则返回给用户错误的信息,让用户决定如何去做。**乐观锁适用于读操作多的场景,这样可以提高程序的吞吐量。

  • 悲观锁

悲观锁的基本思想是,我们认为数据修改产生冲突的概率比较大,所以在更新之前,我们显示的对要修改的记录进行加锁,直到自己修改完再释放锁。加锁期间只有自己可以进行读写,其他事务只能读不能写。

  • 乐观锁和悲观锁都是针对select而言的
    比如在商品抢购中,用户购买后库存需要减1,而很多用户同时购买时,读出来的库存数量一样,然后多个用户同时用该库存去减1。

  • 这种做法必然会出现很大的漏洞,如果向在淘宝,京东出现这种情况,你就可以打包回家种地了

  • 这种情况如何解决呢,其实可以使用悲观锁进行解决,说白了也就是排他锁。用户进来查库存的时候,就加上排他锁,等他所有操作完成后,再释放排他锁,让其他人进来
    不让用户等待,就可以使用乐观锁方式解决,乐观锁一般靠表的设计和时间戳来实现
    一般是在表中添加version或者timestamp时间戳字段

  • 这样就会保证如果更新失败,就表示有其他程序更新了数据库,就可以通过重试解决
    update table set num=num-1 where id=10 and version=12


总结

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值