您现在的位置是:网站首页> 编程资料编程资料

开发人员为什么必须要了解数据库锁详解_Mysql_

2023-05-27 400人已围观

简介 开发人员为什么必须要了解数据库锁详解_Mysql_

1.锁?

1.1何为锁

锁在现实中的意义为:封闭的器物,以钥匙或暗码开启。在计算机中的锁一般用来管理对共享资源的并发访问,比如我们java同学熟悉的Lock,synchronized等都是我们常见的锁。当然在我们的数据库中也有锁用来控制资源的并发访问,这也是数据库和文件系统的区别之一。

1.2为什么要懂数据库锁?

通常来说对于一般的开发人员,在使用数据库的时候一般懂点DQL(select),DML(insert,update,delete)就够了。

小明是一个刚刚毕业在互联网公司工作的Java开发工程师,平常的工作就是完成PM的需求,当然在完成需求的同时肯定逃脱不了spring,springmvc,mybatis的那一套框架,所以一般来说sql还是自己手写,遇到比较复杂的sql会从网上去百度一下。对于一些比较重要操作,比如交易啊这些,小明会用spring的事务来对数据库的事务进行管理,由于数据量比较小目前还涉及不了分布式事务。

前几个月小明过得都还风调雨顺,知道有一天,小明接了一个需求,商家有个配置项,叫优惠配置项,可以配置买一送一,买一送二等等规则,当然这些配置是批量传输给后端的,这样就有个问题每个规则都得去匹配他到底是删除还是添加还是修改,这样后端逻辑就比较麻烦,聪明的小明想到了一个办法,直接删除这个商家的配置,然后全部添加进去。小明马上开发完毕,成功上线。

开始上线没什么毛病,但是日志经常会出现一些mysql-insert-deadlock异常。由于小明经验比较浅,对于这类型的问题第一次遇见,于是去问了他们组的老司机-大红,大红一看见这个问题,然后看了他的代码之后,输出了几个命令看了几个日志,马上定位了问题,告诉了小明:这是因为delete的时候会加间隙锁,但是间隙锁之间却可以兼容,但是插入新的数据的时候就会因为插入意向锁会被间隙锁阻塞,导致双方被资源被互占,导致死锁。小明听了之后似懂非懂,由于大红的事情比较多,不方便一直麻烦大红,所以决定自己下来自己想。下班过后,小明回想大红说的话,什么是间隙锁,什么是插入意向锁,看来作为开发者对数据库不应该只会写SQL啊,不然遇到一些疑难杂症完全没法解决啊。想完,于是小明就踏上了学习Mysql锁这条不归之路。

2.InnoDB

2.1mysql体系架构

小明没有着急去了解锁这方面的知识,他首先先了解了下Mysql体系架构: 

可以发现Mysql由连接池组件、管理服务和工具组件、sql接口组件、查询分析器组件、优化器组件、 缓冲组件、插件式存储引擎、物理文件组成。

小明发现在mysql中存储引擎是以插件的方式提供的,在Mysql中有多种存储引擎,每个存储引擎都有自己的特点。随后小明在命令行中打出了:

 show engines \G;

一看原来有这么多种引擎。

又打出了下面的命令,查看当前数据库默认的引擎:

 show variables like '%storage_engine%';

小明恍然大悟:原来自己的数据库是使用的InnoDB,依稀记得自己在上学的时候好像听说过有个引擎叫MyIsAM,小明想这两个有啥不同呢?马上查找了一下资料:

对比项InnoDBMyIsAM
事务支持不支持
支持MVCC行锁表锁
外键支持不支持
存储空间存储空间由于需要高速缓存,较大可压缩
适用场景有一定量的update和Insert大量的select

小明大概了解了一下InnoDB和MyIsAM的区别,由于使用的是InnoDB,小明就没有过多的纠结这一块。

2.2事务的隔离性

小明在研究锁之前,又回想到之前上学的时候教过的数据库事务隔离性,其实锁在数据库中其功能之一也是用来实现事务隔离性。而事务的隔离性其实是用来解决,脏读,不可重复读,幻读几类问题。

2.2.1 脏读

一个事务读取到另一个事务未提交的更新数据。 什么意思呢?

时间点事务A事务B
1begin;
2select * from user where id = 1;begin;
3
update user set namm = 'test' where id = 1;
4select * from user where id = 1;
5commit;commit;

在事务A,B中,事务A在时间点2,4分别对user表中id=1的数据进行了查询了,但是事务B在时间点3进行了修改,导致了事务A在4中的查询出的结果其实是事务B修改后的。破坏了数据库中的隔离性。

2.2.2 不可重复读

在同一个事务中,多次读取同一数据返回的结果不同,和脏读不同的是这里读取的是已经提交过后的。

时间点事务A事务B
1begin;
2select * from user where id = 1;begin;
3
update user set namm = 'test' where id = 1;
4
commit;
5select * from user where id = 1;
6commit;
在事务B中提交的操作在事务A第二次查询之前,但是依然读到了事务B的更新结果,也破坏了事务的隔离性。

在事务B中提交的操作在事务A第二次查询之前,但是依然读到了事务B的更新结果,也破坏了事务的隔离性。 

2.2.3 幻读

一个事务读到另一个事务已提交的insert数据。

时间点事务A事务B
1begin;
2select * from user where id > 1;begin;
3
insert user select 2;
4
commit;
5select * from user where id > 1;
6commit;

在事务A中查询了两次id大于1的,在第一次id大于1查询结果中没有数据,但是由于事务B插入了一条Id=2的数据,导致事务A第二次查询时能查到事务B中插入的数据。

事务中的隔离性:

隔离级别脏读不可重复读幻读
未提交读(RUC)NONONO
已提交读(RC)YESNONO
可重复读(RR)YESYESNO
可串行化YESYESYES

小明注意到在收集资料的过程中,有资料写到InnoDB和其他数据库有点不同,InnoDB的可重复读其实就能解决幻读了,小明心想:这InnoDB还挺牛逼的,我得好好看看到底是怎么个原理。

2.3 InnoDB锁类型

小明首先了解一下Mysql中常见的锁类型有哪些:

2.3.1 S or X

在InnoDb中实现了两个标准的行级锁,可以简单的看为两个读写锁:

  • S-共享锁:又叫读锁,其他事务可以继续加共享锁,但是不能继续加排他锁。
  • X-排他锁: 又叫写锁,一旦加了写锁之后,其他事务就不能加锁了。

兼容性:是指事务A获得一个某行某种锁之后,事务B同样的在这个行上尝试获取某种锁,如果能立即获取,则称锁兼容,反之叫冲突。

纵轴是代表已有的锁,横轴是代表尝试获取的锁。

.XS
X冲突冲突
S冲突兼容

2.3.2 意向锁

意向锁在InnoDB中是表级锁,和他的名字一样他是用来表达一个事务想要获取什么。意向锁分为:

  • 意向共享锁:表达一个事务想要获取一张表中某几行的共享锁。
  • 意向排他锁:表达一个事务想要获取一张表中某几行的排他锁。

这个锁有什么用呢?为什么需要这个锁呢? 首先说一下如果没有这个锁,如果要给这个表加上表锁,一般的做法是去遍历每一行看看他是否有行锁,这样的话效率太低,而我们有意向锁,只需要判断是否有意向锁即可,不需要再去一行行的去扫描。

在InnoDB中由于支持的是行级的锁,因此InnboDB锁的兼容性可以扩展如下:

.IXISXS
IX兼容兼容冲突冲突
IS兼容兼容冲突兼容
X冲突冲突冲突冲突
S冲突兼容冲突兼容

2.3.3 自增长锁

自增长锁是一种特殊的表锁机制,提升并发插入性能。对于这个锁有几个特点:

  • 在sql执行完就释放锁,并不是事务执行完。
  • 对于Insert...select大数据量插入会影响插入性能,因为会阻塞另外一个事务执行。
  • 自增算法可以配置。

在MySQL5.1.2版本之后,有了很多优化,可以根据不同的模式来进行调整自增加锁的方式。小明看到了这里打开了自己的MySQL发现是5.7之后,于是便输入了下面的语句,获取到当前锁的模式:

 mysql> show variables like 'innodb_autoinc_lock_mode'; +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | innodb_autoinc_lock_mode | 2 | +--------------------------+-------+ 1 row in set (0.01 sec)

在MySQL中innodbautoinclock_mode有3种配置模式:0、1、2,分别对应”传统模式”, “连续模式”, “交错模式”。

  • 传统模式:也就是我们最上面的使用表锁。
  • 连续模式:对于插入的时候可以确定行数的使用互斥量,对于不能确定行数的使用表锁的模式。
  • 交错模式:所有的都使用互斥量,为什么叫交错模式呢,有可能在批量插入时自增值不是连续的,当然一般来说如果不看重自增值连续一般选择这个模式,性能是最好的。

2.4InnoDB锁算法

小明已经了解到了在InnoDB中有哪些锁类型,但是如何去使用这些锁,还是得靠锁算法。

2.4.1 记录锁(Record-Lock)

记录锁是锁住记录的,这里要说明的是这里锁住的是索引记录,而不是我们真正的数据记录。

  • 如果锁的是非主键索引,会在自己的索引上面加锁之后然后再去主键上面加锁锁住.
  • 如果没有表上没有索引(包括没有主键),则会使用隐藏的主键索引进行加锁。
  • 如果要锁的列没有索引,则会进行全表记录加锁。

2.4.2 间隙锁

间隙锁顾名思义锁间隙,不锁记录。锁间隙的意思就是锁定某一个范围,间隙锁又叫gap锁,其不会阻塞其他的gap锁,但是会阻塞插入间隙锁,这也是用来防止幻读的关键。

2.4.3 next-key锁

这个锁本质是记录锁加上gap锁。在RR隔离级别下(InnoDB默认),Innodb对于行的扫描锁定都是使用此算法,但是如果查询扫描中有唯一索引会退化成只使用记录锁。为什么呢? 因为唯一索引能确定行数,而其他索引不能确定行数,有可能在其他事务中会再次添加这个索引的数据会造成幻读。

这里也说明了为什么Mysql可以在RR级别下解决幻读。

2.4.4 插入意向锁

插入意向锁Mysql官方对其的解释:

An insert intention lock is a type of gap lock set by INSERT operations prior to row insertion. This lock signals the intent to insert in such a way that mul

-六神源码网