mysql 锁 读锁 写锁 共享锁 排它锁 表锁 行锁 悲观锁 乐观锁

2022年10月16日13:16:44

innodb和myisam的区别是innodb支持事务和行锁

mysql的锁分类

按照数据操作类型来分:

读锁:读锁也可以叫(共享锁)字母S

写锁:(排它锁)字母x

#创建读锁和写锁


#开启事务
begin;

#设置加锁的方式  对读取的几率加s锁(读锁)
select .... for share;
#或
select ..... lock in share Mode;

#对读取的记录加x锁 (写锁)(
select ....  for update;


commit;
#或rollback;

备注:对于innodb来说 读锁和写锁可以加在表上或者行上

按照锁粒度角度划分

表锁:

表级别的s锁 和x锁 (myisam)

#查看当前表有没有加锁
shou open tables;

#对表t加表级别的 S锁 
LOCK TABLES t READ ;

#对表t加表级别的 X锁 
LOCK TABLES t WRITE ;

#释放锁

unlock tables;

备注:innodb也可以但是会性能低

表级别的意向锁   innodb

分为:意向共享锁 和 意向排它锁

#事务要获取某些行的 S 锁,必须先获得表的 IS 锁  意向共享锁
SELECT column FROM table ... LOCK IN SHARE MODE;

#事务要获取某些行的 X 锁,必须先获得表的 IX 锁 意向排他锁
SELECT column FROM table ... FOR UPDATE;

自增锁 auto_increment

元数据锁(MDL锁)

当对一个表做增删改查拆作的时候,加MDL读锁:当要对表做结构变更操作的时候,加MDL写锁。”读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查,读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性,解决了DML和DDL操作之间的一致性问题:不需要显式使用,在访问一个表的时候会被自动加上。

行锁:

记录锁:(LOCK_REC_NOT_GAP)记录锁是有S锁和X锁之分的,称之为 S型记录锁 和 X型记录锁 。

当一个事务获取了一条记录的S型记录锁后,其他事务也可以继续获取该记录的S型记录锁,但不可 以继续获取X型记录锁; 当一个事务获取了一条记录的X型记录锁后,其他事务既不可以继续获取该记录的S型记录锁,也不 可以继续获取X型记录锁。

间隙锁:(LOCK_GAP) 防止插入幻影记录

临键锁:(LOCK_ORDINARY )既想锁住某条记录 ,又想阻止其他事务在该记录前边的 间隙插入新记录

插入意向锁:(LOCK_INSERT_INTENTION)说一个事务在 插入一条记录时需要判断一下插入位置是不是被别的事务加了 gap锁 ( next-key锁 也包含 gap锁 ),如果有的话,插入操作需要等待,直到拥有 gap锁 的那个事务提交。但是InnoDB规定事务在等待的时候也需要在内存中生成一个锁结构,表明有事务想在某个 间隙 中 插入 新记录,但是 现在在等待

页锁:

页锁就是在 页的粒度 上进行锁定,锁定的数据资源比行锁要多,因为一个页中可以有多个行记录。当我 们使用页锁的时候,会出现数据浪费的现象,但这样的浪费最多也就是一个页上的数据行。页锁的开销 介于表锁和行锁之间,会出现死锁。锁定粒度介于表锁和行锁之间,并发度一般

按照态度划分

悲观锁:悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 阻塞 直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁,当其他线程想要访问数据时,都需要阻塞挂起。Java中 synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现。

select.... for update语句执行过程中所有扫描的行都会被锁上,因此在MySQL中用悲观锁必须确定使用了索引,而不是全表扫描,否则将会把整个表锁住。

乐观锁:乐观锁认为对同一数据的并发操作不会总发生,属于小概率事件,不用每次都对数据上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,也就是不采用数据库自身的锁机制,而是通过程序来实现。在程序上,我们可以采用 版本号机制 或者 CAS机制 实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量。在Java中 java.util.concurrent.atomic 包下的原子变量类就是使用了乐观锁的一种实现方式:CAS实现的。

1. 乐观锁的版本号机制
在表中设计一个 版本字段 version ,第一次读的时候,会获取 version 字段的取值。然后对数据进行更新或删除操作时,会执行 UPDATE ... SET version=version+1 WHERE version=version 。此时如果已经有事务对这条数据进行了更改,修改就不会成功。
2. 乐观锁的时间戳机制
时间戳和版本号机制一样,也是在更新提交的时候,将当前数据的时间戳和更新之前取得的时间戳进行比较,如果两者一致则更新成功,否则就是版本冲突。你能看到乐观锁就是程序员自己控制数据并发操作的权限,基本是通过给数据行增加一个戳(版本号或者时间戳),从而证明当前拿到的数据是否最新。

乐观锁 适合 读操作多 的场景,相对来说写的操作比较少。它的优点在于 程序实现 , 不存在死锁
问题,不过适用场景也会相对乐观,因为它阻止不了除了程序以外的数据库操作。
悲观锁 适合 写操作多 的场景,因为写的操作具有 排它性 。采用悲观锁的方式,可以在数据库层
面阻止其他事务对该数据的操作权限,防止 读 - 写 和 写 - 写 的冲突。

按照加锁方式

显性加锁:通过特定的语句进行加锁,我们一般称之为显示加锁

如:select .... lock in share mode
select .... for update

隐性加锁:

情景一:对于聚簇索引记录来说,有一个 trx_id 隐藏列,该隐藏列记录着最后改动该记录的 事务 id 。那么如果在当前事务中新插入一条聚簇索引记录后,该记录的 trx_id 隐藏列代表的的就是当前事务的 事务id ,如果其他事务此时想对该记录添加 S锁 或者 X锁 时,首先会看一下该记录trx_id 隐藏列代表的事务是否是当前的活跃事务,如果是的话,那么就帮助当前事务创建一个 X 锁 (也就是为当前事务创建一个锁结构, is_waiting 属性是 false ),然后自己进入等待状态(也就是为自己也创建一个锁结构, is_waiting 属性是 true )。
情景二:对于二级索引记录来说,本身并没有 trx_id 隐藏列,但是在二级索引页面的 PageHeader 部分有一个 PAGE_MAX_TRX_ID 属性,该属性代表对该页面做改动的最大的 事务id ,如果PAGE_MAX_TRX_ID 属性值小于当前最小的活跃 事务id ,那么说明对该页面做修改的事务都已经提交了,否则就需要在页面中定位到对应的二级索引记录,然后回表找到它对应的聚簇索引记
录,然后再重复 情景一 的做法。

其他锁:

全局锁:就是对 整个数据库实例 加锁。当你需要让整个库处于 只读状态 的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。全局锁的典型使用 场景 是:做 全库逻辑备份 。


Flush tables with read lock   #创建指令

死锁:是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环

一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数innodb_lock_wait_timeout来设置。另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务(将持有最少行级排他锁的事务进行回滚),让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为on ,表示开启这个逻辑

  • 作者:铭曱
  • 原文链接:https://blog.csdn.net/qq_41486847/article/details/123515524
    更新时间:2022年10月16日13:16:44 ,共 3133 字。