MySQL读现象与锁机制
目录
内容概述
1.MySQL读现象
2.数据库锁机制
内容详细
1.MySQL读现象
数据库管理软件的“读现象”指的是当多个事务并发执行时,在读取数据方面可能碰到的问题,包括有脏读、不可重复读和幻读。
脏读
脏读指读到未提交的数据
不可重复读
不可重复读:一个事务下,两次读取数据不一致(侧重内容数据的修改)
幻读
幻读:幻读本质是一种特殊的不可重复读,事务A按照一定条件进行数据读取,期间事务B插入了相同搜索条件的新数据,事务A再次按照原条件进行读取时,发现了事务B新插入的数据称为幻读。(侧重新增或删除,插入数据读到多了一行)
2.数据库锁机制
什么是锁?为什么要加入锁机制?
锁是计算机协调多个进程或线程并发访问某一资源的机制
为何要加入锁机制?
因为在数据库中,数据需要被不同的用户共享,当并发事务同时访问一个共享的资源时,有可能导致数据不一致、数据无效等问题。
为了应对这些问题,主流数据库都提供了锁机制,锁机制可以将并发的数据访问顺序化,以保证数据库中数据的一致性和有效性。
锁的分类
1.按锁的粒度划分:
行级锁,表级锁,页级锁
2.按锁级别划分:
共享锁,排他锁
3.按使用方式划分:
乐观锁,悲观锁
4.按加锁的方式划分:
自动锁,显示锁
5.按操作划分,可划分为:
DML锁,DDL锁
# 补充:
DML锁:数据锁,用于保护数据的完整性,其中包括行级锁、表级锁
DDL锁:数据字典锁,用于保护数据库对象的结构,比如表、索引等的结构定义。其中包括排他DDL锁、共享DDL锁、可中断解析锁
MySQL中的行级锁、表级锁、页级锁(粒度)
行级锁
行级锁是MySQL中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。
行级锁分为共享锁和排他锁
# 特点:
开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,同样,并发度也是最高的。
支持引擎:InnoDB
行级锁定分为行共享读锁(共享锁)与行独占写锁(排他锁)
共享锁(S):select * from table_name where ... lock in share mode
排他锁(X):select * from table_name where ... for update
表级锁(偏向于读)
表级锁是MySQL中锁定粒度最大的一种锁。表示对当前操作的整张表加锁,实现简单,资源消耗较少,被大部分MySQL存储引擎支持。
MYISAM与INNODB都支持表级锁定
表级锁定分为表共享读锁(共享锁)与表独占锁(排他锁)
# 特点:
开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并且并发度最低
支持引擎:MyISAM、MEMORY、InNoDB
分类:表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)
lock table 表名 read(write),表名 read(write),...;
# 给表加读锁或者写锁
页级锁
页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突少,行级锁冲突少,但速度慢。所以取了折中的页级,一次锁定相邻的一组记录。BDB支持页级锁。
# 特点:开销和加锁时间介于表锁和行锁之间,并发度一般
行级锁之共享锁与排他锁(级别)
行级锁分为共享锁和排他锁两种
与行处理相关的SQL有:
insert、update、delete、select,这四类sql在操作记录行时,可以为行加上锁,但需要知道的是:
1.对于insert、update、delete语句,InnoDB会自动给涉及的数据加锁,而且是排他锁(X)
2.对于普通的select语句,InnoDB不会加任何锁,需要我们手动添加
共享锁(S):select ... lock in share mode; # 查出的记录行都会被锁住
排他锁(X):select ... for update; # 查出的记录行都会被锁住
共享锁
共享锁又称为读锁,简称S锁,共享锁就是多个事务对于同一数据可以共享一把锁,获准共享锁的事务只能读数据,不能修改数据知道释放所有的共享锁,所有共享锁可以支持并发读
如果事务X对数据A加上共享锁后,则其他事务只能对A再加共享锁或不加锁(在其他事务里一定不能再加排他锁,但在事务T自己里是可以自己加的),反之亦然。
# 用法
select ... lock in share mode;
在查询语句后面增加LOCK IN SHARE MODE,Mysql会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的表,而且这些线程读取的是同一个版本的数据。
排他锁
排他锁又称为写锁,简称X锁,排它锁就是不能与其他锁共存,如果一个事务获取了一个数据行的排他锁,其他事务就不能再对该行加任何类型的其他锁(共享锁和排他锁),但是获取排他锁的事务是可以对数据就行读取和修改
# 用法:
select ... for update;
在查询语句后面增加for update,Mysql会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。
# 特例:加过排他锁的数据行在其他事务中是不能修改数据的,也不能通过for update 和 lock in share mode锁的方式查询数据,但可以直接通过select ... from...查询数据,因为普通select查询没有
MySQL死锁现象
1.关于死锁问题的知识储备
在MySQL中,行级锁并不是直接锁记录,而是锁索引,索引分为主键索引与非主键索引
如果一条SQL语句操作了主键索引,MySQL就会锁定这条主键索引
如果一条SQL语句操作了非主键索引,MySQL会优先锁定该非主键索引,再锁定相关的主键索引
在update、delete操作时,MySQL不仅锁定where条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next—key locking
2.死锁产生的本质原理
死锁的发生与否,并不在于事务中有多少条SQL语句,死锁的关键在于:两个(或以上)的session加锁的顺序不一致。
分析死锁的原因:
分析每条SQL语句的加锁规则,分析出每条语句的加锁顺序,然后检查多个并发SQL间是否存在以相反的顺序加锁的情况
MyISAM中是不会产生死锁的,因为MyISAM总是一次性获得所需的全部锁,要么全部满足,要么全部等待。而在InnoDB中,锁是逐步获得的,就造成了死锁的可能。
在两个或多个任务中,如果每个任务锁定了其他任务试图锁定的资源,此时会造成这些任务永久阻塞,从而出现死锁。例如:事务A获取了行1的共享锁,事务B获取了行2的共享锁
排他锁,等待事务 B 完成并释放其对行 2 持有的共享锁之前被阻塞。
排他锁,等待事务 A 完成并释放其对行 1 持有的共享锁之前被阻塞。
事务 B 完成之后事务 A 才能完成,但是事务 B 由事务 A 阻塞。该条件也称为循环依赖关系:事务 A 依赖于事务 B,事务 B 通过对事务 A 的依赖关系关闭循环。
# 产生条件
虽然进程在运行过程中,可能发生死锁,但死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件。
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源......Pn正在等待已被P0占用的资源。