MySQL锁
1.锁知识点脑图
2.锁种类释义
2.1锁概述
锁是计算机协调多个进程或线程并发访问某一资源的机制。锁保证数据并发访问的一致性、有效性;锁冲突也是影响数据库并发访问性能的一个重要因素。锁是Mysql在服务器层和存储引擎层的的并发控制。
加锁是消耗资源的,锁的各种操作,包括获得锁、检测锁是否是否已解除、释放锁等。
2.2粒度划分锁
粒度:mysql中代指锁的级别
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低对整张表加锁。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
2.3使用方式
共享锁:是一种读锁,当前事务加锁后其他事务可以读但不可以写.存储引擎不同实现的功能也不一样.
排他锁:是一种写锁,当前事务加锁后其他事务不可以读也不可以写.存储引擎不同实现的功能也不一样.
2.3逻辑划分
悲观锁和乐观锁是在很多框架都存在的一种思想,不要狭义地认为它们是某一种框架的锁机制
乐观锁:乐观锁需要程序员自己去实现的锁机制
悲观锁:悲观锁的实现是基于Mysql自身的锁机制实现
2.4各存储引擎对锁的支持
MyISAM 和 MEMORY 存储引擎采用的是表级锁(table-level locking)
BDB 存储引擎采用的是页面锁(page-level locking),但也支持表级锁
InnoDB 存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。
3.MyISAM锁
3.1支持的粒度锁
MyISAM中默认支持的表级锁有两种:即[共享读锁]和[独占写锁]。表级锁在MyISAM和InnoDB的存储引擎中都支持,但是InnoDB默认支持的是行锁。
3.2锁的使用方式
独占写锁:当一个线程获取到表级写锁后,只能由该线程对表进行读写操作,别的线程必须等待该线程释放锁以后才能操作
共享读锁:****当一个线程获取到表级读锁后,该线程只能读取数据不能修改数据,其它线程也只能加读锁,不能加写锁
3.3锁竞争查看方法
MyISAM存储引擎中,可以通过查询变量来查看并发场景锁的争夺情况,具体执行下面的sql语句:
show status like 'table%';
主要是查看
table_locks_waited
和table_locks_immediate
的值的大小分析锁的竞争情况。
Table_locks_immediate
:表示能够立即获得表级锁的锁请求次数;Table_locks_waited
表示不能立即获取表级锁而需要等待的锁请求次数分析,「值越大竞争就越严重」。
3.4并行插入
解锁和释放锁都是Mysql底层隐式的执行的
在我们平时执行select语句的时候就会隐式的加读锁,执行增、删、改的操作时就会隐式的执行加写锁。
MyISAM存储引擎中,虽然读写操作是串行化的,但是它也支持并发插入,这个需要设置内部变量concurrent_insert
的值。
它的值有三个值0、1、2
。可以通过以下的sql查看concurrent_insert
的默认值为「AUTO(或者1)」。
concurrent_insert的值为NEVER (or 0)
表示不支持比并发插入;值为AUTO(或者1)
表示在MyISAM表中没有被删除的行,运行另一个线程从表尾插入数据;值为ALWAYS (or 2)
表示不管是否有删除的行,都允许在表尾插入数据。
3.5锁调度
MyISAM存储引擎中,「假如同时一个读请求,一个写请求过来的话,它会优先处理写请求」,因为MyISAM存储引擎中认为写请求比都请求重要。
这样就会导致,「假如大量的读写请求过来,就会导致读请求长时间的等待,或者"线程饿死",因此MyISAM不适合运用于大量读写操作的场景」,这样会导致长时间读取不到用户数据,用户体验感极差。
当然可以通过设置low-priority-updates
参数,设置请求链接的优先级,使得Mysql优先处理读请求。
4.InnoDB锁
4.1支持的粒度锁
行锁是InnoDB默认的支持的锁机制MyISAM不支持行锁,这个也是InnoDB和MyISAM的区别之一。另外innoDB支持事务,MyISAM不支持事务.
InnoDB中除了有「表锁」和「行级锁」的概念,还有Gap Lock(间隙锁)、Next-key Lock锁,「间隙锁主要用于范围查询的时候,锁住查询的范围,并且间隙锁也是解决幻读的方案」。
InnoDB中的行级锁是「对索引加的锁,在不通过索引查询数据的时候,InnoDB就会使用表锁」
4.2使用方式
InnoDB的行锁也是分为行级「共享读锁(S锁)「和」排它写锁(X锁)」,原理特点和MyISAM的表级锁两种模式是一样的。
执行非索引条件查询执行的是表锁。
执行索引查询是否是加行锁,还得看Mysql的执行计划,可以通过explain关键字来查看。
用普通键索引的查询,遇到索引值相同的,也会对其他的操作数据行的产生影响。
4.3InnoDB间隙锁
当我们使用范围条件查询而不是等值条件查询的时候,InnoDB就会给符合条件的范围索引加锁,在条件范围内并不存的记录就叫做"间隙(GAP)"
大家大概都知道在事务的四大隔离级别中,不可重复读会产生幻读的现象,只能通过提高隔离级别到串行化来解决幻读现象。
但是Mysql中的不可重复是已经解决了幻读问题,它通过引入间隙锁的实现来解决幻读,通过给符合条件的间隙加锁,防止再次查询的时候出现新数据产生幻读的问题。
4.4死锁
死锁在InnoDB中才会出现死锁,MyISAM是不会出现死锁,因为MyISAM支持的是表锁,一次性获取了所有得锁,其它的线程只能排队等候。
而InnoDB默认支持行锁,获取锁是分步的,并不是一次性获取所有得锁,因此在锁竞争的时候就会出现死锁的情况。
虽然InnoDB会出现死锁,但是并不影响InnoDB最受欢成为迎的存储引擎,MyISAM可以理解为串行化操作,读写有序,因此支持的并发性能低下。
- 死锁案例
当前有表如下:
其中name="spike"的有两条数据,并且name字段为普通索引,分别是id=3和id=4的数据行,现在假设有两个事务分别执行下面的两条sql语句:
// session1
update user_info set age = 19 where name ='spike';
// session2执行
select * from user_info where id = 3 or id = 4;
其中session1执行的sql获取的数据行是两条数据,假设先获取到第一个id=3的数据行,然后cpu的时间分配给了另一个事务,另一个事务执行查询操作获取了第三行数据也就是id=3的数据行。
当事务2继续执行的时候获取到id=3的数据行,锁定了id=3的数据行,此时cpu又将时间分配给了第一个事务,第一个事务执行准备获取第二行数据的锁,发现已经被其他事务获取了,它就处于等待的状态。
当cpu把时间有分配给了第二个事务,第二个事务准备获取第一行数据的锁发现已经被第一个事务获取了锁,这样就行了死锁,两个事务彼此之间相互等待。
4.5死锁解决方案
首先要解决死锁问题,在程序的设计上,当发现程序有高并发的访问某一个表时,尽量对该表的执行操作串行化,或者锁升级,一次性获取所有的锁资源。
然后也可以设置参数innodb_lock_wait_timeout
,超时时间,并且将参数innodb_deadlock_detect
打开,当发现死锁的时候,自动回滚其中的某一个事务。
5.总结
上面详细的介绍了MyISAM和InnoDB两种存储引擎的锁机制的实现,并进行了测试。
MyISAM的表锁分为两种模式:「共享读锁」和「排它写锁」。获取的读锁的线程对该数据行只能读,不能修改,其它线程也只能对该数据行加读锁。
获取到写锁的线程对该数据行既能读也能写,对其他线程对该数据行的读写具有排它性。
MyISAM中默认写优先于去操作,因此MyISAM一般不适合运用于大量读写操作的程序中。
InnoDB的行锁虽然会出现死锁的可能,但是InnoDB的支持的并发性能比MyISAM好,行锁的粒度最小,一定的方法和措施可以解决死锁的发生,极大的发挥InnoDB的性能。
InnoDB中引入了间隙锁的概念来决解出现幻读的问题,也引入事务的特性,通过事务的四种隔离级别,来降低锁冲突,提高并发性能。
6.参考文档
本文参考文档[大厂面试官必问的Mysql锁机制] (https://zhuanlan.zhihu.com/p/150101667),大哥文档比较详细我这里是按自己理解做的笔记