数据库锁
参考文档:《MySQL 是怎样运行的:从根儿上理解 MySQL》
参考博客:
https://www.zhihu.com/question/437140380
https://blog.csdn.net/w1014074794/article/details/126381482
https://www.cnblogs.com/Aiapple/p/12751803.html
加锁的目的
- 对数据加锁是为了解决事务的隔离性问题,让事务之间相互不影响,每个事务进行操作的时候都必须先对数据加上一把锁,防止其他事务同时操作数据。提交或回滚事务会释放锁。
锁是基于什么实现的
-
数据库里面的锁是基于索引实现的,在Innodb中我们的锁都是作用在索引上面的,当我们的SQL命中索引时,那么锁住的就是命中条件内的索引节点(行锁),如果没有命中索引的话,那我们锁的就是整个索引树(表锁)。(insert语句怎么加锁?锁住整个表吗?)
1、如果一条sql 语句通过主键索引命中记录,Mysql 就会锁定这条语句命中的主键索引(或称聚簇索引); 2、如果一条语句通过非主键索引(或称辅助索引)命中记录,MySQL会先锁定该非主键索引,再锁定相关的主键索引。 3、如果没有索引,InnoDB 会通过隐藏的聚簇索引来对记录加锁。也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表级锁一样。
在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。 1、在不通过索引条件查询的时候,InnoDB 的效果就相当于表锁 2、当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论 是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。 3、由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,所以即便你的sql语句访问的是不同的记录行,但如果命中的是相同的被锁住的索引键,也还是会出现锁冲突的。 4、即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同 执行计划的代价来决定的,如果 MySQL 认为全表扫 效率更高,比如对一些很小的表,它 就不会使用索引,这种情况下 InnoDB 将锁住所有行,相当于表锁。因此,在分析锁冲突时, 别忘了检查 SQL 的执行计划,以确认是否真正使用了索引
锁的分类
-
一、按锁的粒度划分,可分为
行级锁
、表级锁
、页级锁。
(mysql支持)在DBMS中,可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎 )。 行级锁 行级锁是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> lock table employee write; Query OK, 0 rows affected (0.00 sec) mysql> show open tables where in_use>= 1; +----------+----------+--------+-------------+ | Database | Table | In_use | Name_locked | +----------+----------+--------+-------------+ | ttt | employee | 1 | 0 | +----------+----------+--------+-------------+ 1 row in set (0.00 sec) mysql> unlock tables; -- UNLOCK TABLES释放被当前会话持有的任何锁 Query OK, 0 rows affected (0.00 sec) mysql> show open tables where in_use>= 1; Empty set (0.00 sec) 页级锁 页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁 特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
-
二、按锁级别划分,可分为
共享锁
、排他锁
(都是悲观锁)共享锁又称读锁,简称S锁。当一个事务对数据加上读锁之后,其他事务只能对该数据加读锁,而无法对数据加写锁,直到所有的读锁释放之后其他事务才能对其进行加写锁。 加了共享锁之后,无法再加排它锁,这也就可以避免读取数据的时候会被其它事务修改,从而导致重复读问题。 排他锁又称写锁,简称X锁;当一个事务对数据加上写锁之后,其他事务将不能再为数据加任何锁,直到该锁释放之后,其他事务才能对数据进行加锁。加了排他锁之后,其它事务就无法再对数进行读取和修改,所以也就避免了脏写和脏读的问题。update加锁类型为排他锁
-
三、按使用方式划分,可分为
乐观锁
、悲观锁
悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。
乐观锁认为对同一个数据并发操作不会总发生,是小概率事件,因此不用每次对数据进行更新或者删除。
- 四、按锁状态划分,可分为
意向共享锁
、意向排它锁
意向锁是表级锁,其设计目的主要是为了在一个事务中揭示下一行将要被请求锁的类型。
意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。意向锁是InnoDB自动加的,不需要用户干预。
如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁
(1)意向共享锁(IS):事务打算给数据行共享锁;,事务在给一个数据行加共享锁前必须先取得该表的IS锁
(2)意向排他锁(IX)事务打算给数据行加排他锁;事务在给一个数据行加排他锁前必须先取得该表的IX锁
解决并发事务问题的两种基本⽅式
并发事务访问相同记录的情况⼤致可以划分为3种:
-
读-读情况:即并发事务相继读取相同的记录。
读取操作本身不会对记录有影响,所以允许这种情况的发⽣。 -
写-写情况:即并发事务相继对相同的记录做出改动。
在这种情况下会发⽣脏写的问题,任何⼀种隔离级别都不允许这种问题的发⽣。所以在多个未提交事务相继对⼀条记录做改动时,需要通过锁来让它们排队执⾏, 锁有把两个⽐较重要的属性- trx信息:代表这个锁结构是哪个事务⽣成的。
- is_waiting:代表当前事务是否在等待。
当⼀个事务想对这条记录做改动时,⾸先会看看内存中有没有与这条记录关联的锁结构,当没有的时候就会在内存中⽣成⼀个锁结构与之关联。
- 读-写或写-读情况
这种情况下可能发⽣脏读、不可重复读、幻读的问题。为了解决这些问题,有两种可选的解决⽅案:- ⽅案⼀:读操作利⽤多版本并发控制(MVCC),写操作进⾏加锁。
- ⽅案⼆:读、写操作都采⽤加锁的⽅式,⽐⽅在银⾏存款的事务。
很明显,采⽤MVCC⽅式的话,读-写操作彼此并不冲突,性能更⾼,采⽤加锁⽅式的话,读-写操作彼此需要排队执⾏,影响性能。
⼀致性读(Consistent Reads)
事务利⽤MVCC进⾏的读取操作称之为⼀致性读,或者称之为快照读。所有普通的SELECT语句(plain SELECT)在READ COMMITTED、REPEATABLE READ隔离级别下都算是⼀致性读,⼀致性读并不会对表中的任何记录做加锁操作,其他事务可以⾃由的对表中的记录做改动。
锁定读(Locking Reads)
共享锁和独占锁
在使⽤加锁的⽅式解决问题时,由于既要允许读-读情况不受影响,⼜要使写-写、读-写或写-读情况中的操作相互阻塞,所以给锁分了个类:
-
共享锁,英⽂名:Shared Locks,简称S锁。在事务要读取⼀条记录时,需要先获取该记录的S锁。
-
独占锁,也常称排他锁,英⽂名:Exclusive Locks,简称X锁。在事务要改动⼀条记录时,需要先获取该记录的X锁。
兼容性 | X | S |
---|---|---|
X | 不兼容 | 不兼容 |
S | 不兼容 | 兼容 |
- 对读取的记录加S锁:
SELECT ... LOCK IN SHARE MODE;
- 对读取的记录加X锁:
SELECT ... FOR UPDATE;
写操作
平常所⽤到的写操作⽆⾮是DELETE、UPDATE、INSERT这三种:
-
DELETE:
对⼀条记录做DELETE操作的过程其实是先在B+树中定位到这条记录的位置,然后获取⼀下这条记录的X锁,然后再执⾏delete mark操作。我们也可以把
这个定位待删除记录在B+树中位置的过程看成是⼀个获取X锁的锁定读。 -
UPDATE:
在对⼀条记录做UPDATE操作时分为三种情况:-
如果未修改该记录的键值并且被更新的列占⽤的存储空间在修改前后未发⽣变化,则先在B+树中定位到这条记录的位置,然后再获取⼀下记录的X锁,最后在原记录的位置进⾏修改操作。其实我们也可以把这个定位待修改记录在B+树中位置的过程看成是⼀个获取X锁的锁定读。
-
如果未修改该记录的键值并且⾄少有⼀个被更新的列占⽤的存储空间在修改前后发⽣变化,则先在B+树中定位到这条记录的位置,然后获取⼀下记录的X锁,将该记录彻底删除掉(就是把记录彻底移⼊垃圾链表),最后再插⼊⼀条新记录。这个定位待修改记录在B+树中位置的过程看成是⼀个获取X锁的锁定读,新插⼊的记录由INSERT操作提供的隐式锁进⾏保护。
-
如果修改了该记录的键值,则相当于在原记录上做DELETE操作之后再来⼀次INSERT操作,加锁操作就需要按照DELETE和INSERT的规则进⾏了。
-
-
INSERT:
⼀般情况下,新插⼊⼀条记录的操作并不加锁,设计InnoDB的⼤叔通过⼀种称之为隐式锁的东东来保护这条新插⼊的记录在本事务提交前不被别的事务访问
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY