MySQL锁

MySQL中的锁

MySQL 中锁的概念是用来控制多个事务对同一个数据进行访问的机制,能够保证并发访问时的数据一致性。
MySQL中的锁是用于协调多个进程或线程并发访问数据库资源的机制。在数据库中,除了传统的计算资源(如CPU、RAM、I/O)的争用外,数据也是一种供许多用户共享的资源。因此,锁对数据库而言尤为重要,尤其是在多用户环境下,它可以保证数据库的一致性和完整性。
MySQL锁是计算机协调多个进程或者线程并发访问某一资源的机制。在数据库中,为了保证数据并发访问的一致性、有效性,锁的应用非常重要,其复杂度也相对较高。
在 MySQL 中使用锁可以帮助开发者控制多事务对同一份数据的访问,确保数据一致性和并发性能。选择合适的锁机制需要根据具体的业务需求和并发控制的粒度来决定。

MySQL的锁分类

锁的粒度
全局锁:对整个数据库实例加锁,加锁后整个实例就处于只读状态,对于后面的DML语句、DDL语句以及更新操作的事务提交语句都会被阻塞。典型的场景应用是数据备份。
表级锁:锁定整张表,发生锁冲突的概率最高,并发度低。表级锁又分为表锁、元数据锁、意向锁等。
行级锁:针对数据库表中的具体行进行锁定。InnoDB存储引擎支持行级锁。行级锁是 MySQL 中比较常用的一种锁机制,用于控制对表中单行记录进行访问的并发控制。行级锁分为共享锁和排它锁,允许多个事务同时对同一张表的不同行进行访问。
页锁:介于行锁和表锁之间,MySQL的某些存储引擎可能使用这种锁。
锁的性质:
共享锁(Shared Lock):允许多个事务同时读取同一份数据,但在任何时刻只允许一个事务修改数据。共享锁也称为读锁,多个事务可以同时持有同一份数据的共享锁,读取数据但不能修改数据。共享锁之间不会互相阻塞,也不会和排它锁产生冲突。
排他锁(Exclusive Lock):只允许一个事务独占访问数据,其他事务无法同时读取或修改数据。排他锁也称为写锁,只有一个事务可以持有锁,且该事务可以读取和修改数据。排他锁和其他任何锁都存在冲突,包括共享锁、排他锁和意向锁。
乐观锁(Optimistic Lock):基于数据版本记录机制实现,认为冲突不太可能发生,在操作数据时进行检查,如果冲突则回滚。
悲观锁(Pessimistic Lock):认为冲突很可能会发生,在数据处理开始之前就将数据锁定。
意向锁(Intention Lock): 意向锁是一种表级别的锁,用于表示事务将要对表进行何种类型的操作(读或者写)。意向锁分为两种:意向共享锁(IS)和意向排他锁(IX)。当一个事务需要在一张表上加锁时,MySQL 会先尝试获取该表对应的意向锁,以便判断事务对该表的操作类型是否与已有锁产生冲突。
锁的使用方式:
使用SELECT … FOR SHARE语句来获取共享锁。
使用SELECT … FOR UPDATE语句来获取排他锁。
使用LOCK TABLES语句来获取表锁。
锁的冲突和死锁:
当多个事务试图以不兼容的方式锁定资源时,会发生锁冲突。
死锁是指两个或更多的事务在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法向前推进。
理解MySQL的锁机制对于数据库的性能优化和并发控制非常重要。在实际应用中,需要根据具体的业务场景和需求来选择合适的锁策略。
加锁机制:乐观锁和悲观锁
锁粒度:页锁、表锁、行锁
兼容性:共享锁、排他锁
锁模式:记录锁、gap锁、next_key锁、意向锁锁、插入意向锁
数据库是一个多用户使用的共享资源,比如一个用户表t_user,两个浏览器前面的人登录了同个一个账号,把电话号码改了。当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性(脏读,不可重复读,幻读等),可能产生死锁。为了解决这个问题,加锁是一个非常重要的技术,对实现数据库并发控制是一个好的方案。简单说,当一个执行sql语句的事务想要操作表记录之前,先向数据库发出请求,对你访问的记录集加锁,在这个事务释放这个锁之前,其他事务不能对这些数据进行更新操作。

MySQL的锁粒度分类:

全局锁:
锁定数据库中所有的表。
典型使用场景是进行全库的逻辑备份,确保数据的一致性。
加锁后,整个数据库实例处于只读状态,后续的DML(数据操纵语言,如INSERT、UPDATE、DELETE)写语句、DDL(数据定义语言,如CREATE、ALTER)语句以及更新操作的事务提交语句都会被阻塞。
表级锁:
每次操作锁住整张表。
实现简单,资源消耗较少,被大部分MySQL引擎支持(如MYISAM和INNODB)。
表级锁分为表共享读锁(共享锁)与表独占写锁(排他锁)。
特点:开销小,加锁快;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁:
每次操作锁住对应的行数据。
MySQL中锁定粒度最细的一种锁。
行级锁能大大减少数据库操作的冲突。
行级锁分为共享锁和排他锁。
特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
其他锁
乐观锁:假设在多数情况下,多个事务不会同时修改同一份数据。
悲观锁:假设在多数情况下,多个事务会同时修改同一份数据。因此,在读取数据时立即加锁,以阻止其他事务同时修改。
在使用MySQL锁时,需要根据具体的业务场景和需求来选择合适的锁类型和策略。同时,也需要注意锁的使用可能会导致性能问题,如死锁、锁等待等,因此需要合理设计和优化数据库结构和查询语句,以减少锁的冲突和等待时间。
表锁: 开销小,加锁快;锁定力度大,发生锁冲突概率高,并发度最低;不会出现死锁。
行锁: 开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高。
页锁: 开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度

行级锁、表级锁

行级锁:一种它锁,防止另外事务修改此行;在使用以下语句时,Oracle会自动应用行级锁:INSERT、UPDATE、DELETE、SELECT …
FOR UPDATE [OF columns] [WAIT n | NOWAIT];SELECT … FOR UPDATE语句允许用户一次锁定多条记录进行更新.使用commit或者rollback释放锁。MySql的innodb存储引擎默认是行级锁。特点:开锁大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。适合于有大量按索引更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理系统。
行级锁和表级锁是根据锁的粒度来区分的,行记录,表都是资源,锁是作用在这些资源上的。如果粒度比较小(比如行级锁),可以增加系统的并发量但需要较大的系统开销,会影响到性能,出现死锁,,因为粒度小则操作的锁的数量会增加;如果作用在表上,粒度大,开销小,维护的锁少,不会出现死锁,但是并发是相当昂贵的,因为锁定了整个表就限制了其它事务对这个表中其他记录的访问。

乐观锁和悲观锁

● 乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。
● 悲观锁:每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻止,直到这个锁被释放。
数据库的乐观锁需要自己实现,在表里面添加一个 version 字段,每次修改成功值加 1,这样每次修改的时候先对比一下,自己拥有的 version 和数据库现在的 version 是否一致,如果不一致就不修改,这样就实现了乐观锁。
悲观锁:
Pessimistic Lock正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守悲观态度, 事务每次去操作数据的时候都假//设有其他事务会修改需要访问的数据,所以在访问之前都要求上锁,行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能 真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系 统不会修改数据)。 一个典型的倚赖
数据库的悲观锁调用: select * from account where name=”Erica” for update 这条sql 语句锁定了account 表中所有符合检索条件(name=”Erica”)的记录。 本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。 Hibernate悲欢锁实现:基于数据库锁机制
乐观锁:
Optimistic Lock,和悲欢锁相反,事务每次去操作数据之前,都假设其他事务不会修改这些需要访问的数据 ,所以 在访问之前不要求上锁, 只是在进行更新修改操作的时候判断一下在访问的期间有没有其他人修改数据 了。它适用于多读的应用类型,冲突真的发生比较少的时候就比较好,
这样省去了开销的开销,可以提高吞吐量;但如果是真的经常要发生冲突的,那每次还要去判断进行retry,反倒降低的性能,这个时候悲欢锁比较好。数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

Mysql 层面如何实现乐观锁

在我们表结构中,会新增一个字段就是版本字段
version varchar(255) DEFAULT NULL,
多个线程对同一行数据实现修改操作,提前查询当前最新的 version 版本号码,
作为 update 条件查询,如果当前 version 版本号码发生了变化,则查询不到该数据。
表示如果修改数据失败,则不断重试 ,有从新查询最新的版本实现 update。
需要注意控制乐观锁循环的次数,避免 cpu 飙高的问题。 mysql 的 innodb 引擎中存在行锁的概念
乐观锁
用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加1。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
举例
1、数据库表设计
三个字段,分别是id,value、version
select id,value,version from TABLE where id=#{id}
2、每次更新表中的value字段时,为了防止发生冲突,需要这样操作
update TABLE set value=2,version=version 1 where id=#{id} and version=#{version};

MySQL/Innodb的加锁

MySQL/Innodb的加锁,一直是一个面试中常问的话题。例如,数据库如果有高并发请求,如何保证数据完整性?产生死锁问题如何排查并解决?我在工作过程中,也会经常用到,乐观锁,排它锁,等。
共享/排他锁
InnoDB 呢实现了两种标准的行级锁:共享锁(简称 S 锁)、排他锁(简称 X锁)。
共享锁:简称为 S 锁,在事务要读取一条记录时,需要先获取该记录的 S 锁。
排他锁:简称 X 锁,在事务需要改动一条记录时,需要先获取该记录的 X 锁。
共享锁
共享锁又称读锁 read lock,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
名词解释:共享锁又叫做读锁,所有的事务只能对其进行读操作不能写操作,加上共享锁后在事务结束之前其他事务只能再加共享锁,除此之外其他任何类型的锁都不能再加了。
如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获得共享锁的事务只能读数据,不能修改数据
SELECT * from TABLE where id = “1” lock in share mode; 结果集的数据都会加共享锁
排它锁
排他锁 exclusive lock(也叫writer lock)又称写锁。
排它锁是悲观锁的一种实现,在上面悲观锁也介绍过。
若事务 1 对数据对象A加上X锁,事务 1 可以读A也可以修改A,其他事务不能再对A加任何锁,直到事物 1 释放A上的锁。这保证了其他事务在事物 1 释放A上的锁之前不能再读取和修改A。排它锁会阻塞所有的排它锁和共享锁
读取为什么要加读锁呢:防止数据在被读取的时候被别的线程加上写锁,
使用方式:在需要执行的语句后面加上for update就可以了
排他锁:
名词解释:若某个事物对某一行加上了排他锁,只能这个事务对其进行读写,在此事务结束之前,其他事务不能对其进行加任何锁,其他进程可以读取,不能进行写操作,需等待其释放。
select status from TABLE where id=1 for update;
可以参考之前演示的共享锁,排它锁语句
由于对于表中,id字段为主键,就也相当于索引。执行加锁时,会将id这个索引为1的记录加上锁,那么这个锁就是行锁。

首先对mysql锁进行划分:
按照锁的粒度划分:行锁、表锁、页锁
按照锁的使用方式划分:共享锁、排它锁(悲观锁的一种实现)
还有两种思想上的锁:悲观锁、乐观锁。
InnoDB中有几种行级锁类型:Record Lock、Gap Lock、Next-key Lock
Record Lock:在索引记录上加锁
Gap Lock:间隙锁
Next-key Lock:Record Lock+Gap Lock
行级锁和表级锁是根据锁的粒度来区分的,行记录,表都是资源,锁是作用在这些资源上的。如果粒度比较小(比如行级锁),可以增加系统
的并发量但需要较大的系统开销,会影响到性能,出现死锁,,因为粒度小则操作的锁的数量会增加;如果作用在表上,粒度大,开销小,维护的锁少,不会出现死锁,但是并发是相当昂贵的,因为锁定了整个表就限制了其它事务对这个表中其他记录的访问。
总的来说,InnoDB共有七种类型的锁:
(1)自增锁(Auto-inc Locks);
(2)共享/排它锁(Shared and Exclusive Locks);
(3)意向锁(Intention Locks);(4)插入意向锁(Insert Intention Locks);
(5)记录锁(Record Locks);
(6)间隙锁(Gap Locks);
(7)临键锁(Next-key Locks);

自增锁
自增锁是一种特殊的表级别锁(table-level lock),专门针对事务插入AUTO_INCREMENT类型的列。
最简单的情况,如果一个事务正在往表中插入记录,所有其他事务的插入必须等待,以便第一个事务插入的行,是连续的主键值。

行锁
行锁又分共享锁和排他锁,由字面意思理解,就是给某一行加上锁,也就是一条记录加上锁。
注意:行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁。

表锁
如何加表锁
innodb 的行锁是在有索引的情况下,没有索引的表是锁定全表的.

意向锁
什么是意向锁呢?意向锁是一种不与行级锁冲突的表级锁。未来的某个时刻,事务可能要加共享或者排它锁时,先提前声明一个意向。注意一下,意向锁,是一个表级别的锁哈。

记录锁(Record Lock)
记录锁是最简单的行锁,仅仅锁住一行。如:SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE,如果 C1 字段是主键或者是唯一索引的话,这个 SQL 会加一个记录锁(Record Lock)

间隙锁(Gap Lock)
为了解决幻读问题,InnoDB 引入了间隙锁(Gap Lock)。间隙锁是一种加在两个索引之间的锁,或者加在第一个索引之前,或最后一个索引之后的间隙。它锁住的是一个区间,而不仅仅是这个区间中的每一条数据。

临键锁(Next-Key Lock)
Next-key 锁是记录锁和间隙锁的组合

插入意向锁
插入意向锁,是插入一行记录操作之前设置的一种间隙锁

死锁(Deadlock)
所谓死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。
解除正在死锁的状态有两种方法:
第一种:
1.查询是否锁表
show OPEN TABLES where In_use > 0;
2.查询进程(如果您有SUPER权限,您可以看到所有线程。否则,您只能看到您自己的线程)
show processlist
3.杀死进程id(就是上面命令的id列)
kill id
第二种:
1:查看当前的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
2:查看当前锁定的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
3:查看当前等锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
杀死进程
kill 进程ID
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
虽然不能完全避免死锁,但可以使死锁的数量减至最少。将死锁减至最少可以增加事务的吞吐量并减少系统开销,因为只有很少的事务回滚,而回滚会取消事务执行的所有工作。由于死锁时回滚而由应用程序重新提交。
下列方法有助于最大限度地降低死锁:
(1)按同一顺序访问对象。
(2)避免事务中的用户交互。
(3)保持事务简短并在一个批处理中。
(4)使用低隔离级别。
(5)使用绑定连接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

思静鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值