【享学MySQL】系列:MySQL中的锁机制

我是少侠露飞。博客不仅是笔记,更是一种思考,一种分享。

引言

MySQL的锁机制是面试中的难点,也是时常让开发头疼的问题,今天少侠就做个总结。

表锁、页锁及行锁


  • 表锁,顾名思义就是锁住整张表,当一个事务在操作的时候,其它所有事务只能等待,如果是访问量比较大的表,在并发场景下可谓是灾难级的吞吐量。特点:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突概率高,并发度最低
  • 页锁,就是锁住一页,我们知道MySQL数据都是一页一页加载到内存的,每页16K(比方说一个表只有一个字段,并且占据内存是8000字节,那一次只能加载两行数据到页内)。
  • 行锁,就是每次只锁定被操作的行,是比表锁、页锁维度都要小的一种锁定方式。特别需要注意的是,MySQL的行锁锁住的都是索引,而不是记录,务必牢记!特点:开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高

共享锁和排它锁


  • 共享锁:共享锁又叫做读锁或S锁,加上共享锁后在事务结束之前其他事务只能再加共享锁、只能对其进行读操作不能写操作,除此之外其他任何类型的锁都不能再加了。
 # 加上lock in share mode
SELECT description FROM table lock in share mode;
  • 排他锁:排他锁又叫写锁或X锁,某个事务对数据加上排他锁后,只能这个事务对其进行读写,在此事务结束之前,其他事务不能对其加任何锁,可以读取(因为MySQL读取是不加锁的),不能进行写操作,需等待其释放。
# 加上for update
SELECT description FROM table for update; 

注意,无论是共享锁还是排它锁都是行锁。

乐观锁和悲观锁


在Java中常见的采用CAS算法实现的乐观锁的典型的例子就是原子类,通过CAS自旋实现原子操作的更新,悲观锁通常都是synchronized和Lock实现。那么MySQL中的乐观锁和悲观锁又为何呢?

  • 乐观锁:每次读数据的时候都认为其他人不会修改,所以不会上锁,而是在更新的时候去判断在此期间有没有其他人更新了数据,可以使用版本号机制。在数据库中可以通过为数据表增加一个版本号字段实现。读取数据时将版本号一同读出,数据每次更新时对版本号加一。当我们更新的时候,判断数据库表对应记录的当前版本号与第一次取出来的版本号值进行比对,如果值相等,则予以更新,否则认为是过期数据。乐观锁适用于多读的应用类型,可以提高吞吐量。
# 利用版本号进行CAS更新
UPDATE table t SET t.name='Luffy'  WHERE t.id = 1 AND version = 678768;
  • 悲观锁:每次读数据的时候都认为别人会修改,所以每次在读数据的时候都会上锁,这样别人想读这个数据时就会被阻塞。MySQL中就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在操作之前先上锁。

上节的S锁(共享锁)和X锁(排它锁)都是悲观锁。

间隙锁(Gap Lock)和Next-Key Lock


InnoDB有三种行锁的算法:

  1. Record Lock:单个记录上的锁。
  2. Gap Lock:间隙锁,锁定一个索引范围,但不包含记录本身。
  3. Next-Key Lock:Gap Lock + Record Lock,锁定一个索引范围,并且包含记录本身。

间隙锁使得InnoDB解决幻读问题,加上MVCC使得InnoDB的RR隔离级别实现了串行化级别的效果,并且保留了比较好的并发性能。

定义:当我们用范围条件检索数据时请求共享或排他锁时,InnoDB会给符合条件的已有数据的索引加锁;对于键值在条件范围内但表中并不存在的记录,叫做间隙(GAP),InnoDB也会对这个"间隙"加锁,这种锁机制就是间隙锁。
例如:table表中存在id 1-80,90-99的记录。SELECT * FROM table WHERE id < 100 FOR UPDATE。InnoDB不仅会对id值为1-80,90-99的记录加锁,也会对id在81-89之间(这些记录并不存在)的间隙加锁。这样就能避免事务隔离级别为可重复读下的幻读。

死锁


概念:两个或两个以上的事务在执行过程中,因争夺资源而造成的一种互相等待的现象。
存在条件:1、 互斥条件:一个资源每次只能被一个事务使用。2、 请求与保持条件:一个事务因请求资源而阻塞时,对已获得的资源保持不放。3、不剥夺条件:已获得的资源,在末使用完之前不能强行剥夺。4、循环等待条件:形成一种头尾相接的循环等待关系。
解除正在死锁的状态:撤销其中一个事务。

手写一段死锁代码:

T1:
begin;
select * from user u where u.id=1 for update; 
update user u set u.name='Carson' where id=2;

T2:
begin;
delete from user u where u.id=2;
delete from user u where u.id=1;

多版本并发控制(MVCC)

  • 它使得InnoDB不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能。
  • 实现:InnoDB实现MVCC的方法是它为每一行存储三个额外的隐藏字段:
    1. DB_TRX_ID:一个6byte的标识,每处理一个事务,其值自动+1 ,可以通过语句“show engine innodb status”来查找。
    2. DB_ROLL_PTR: 大小是7byte,指向写到rollback segment(回滚段)的一条undo log记录。
    3. DB_ROW_ID: 大小是6byte,该值随新行插入单调增加。
  • SELECT:返回的行数据需要满足的条件: 1、数据行的创建版本号必须小于等于事务的版本2、行的删除版本号(行中的特殊位被设置为将其标记为已删除)一定是未定义的或者大于当前事务的版本号,确定了当前事务开始之前行没有被删除。
  • INSERT:InnoDB为每个新增行记录当前系统版本号作为创建版本号。
  • DELETE:InnoDB为每个删除行的记录当前系统版本号作为行的删除版本号。
  • UPDATE:InnoDB复制了一条数据。这条数据的版本号使用了系统版本号。它也把系统版本号作为老数据的删除号。
  • 说明:这里的读是不加锁的SELECT等,MVCC实现可重复读使用的是读取undo中的已经提交的数据,是非阻塞的。INSERT操作时"创建时间"=DB_ROW_ID,这时"删除时间"是未定义的;UPDATE时,复制新增行的"创建时间"=DB_ROW_ID,删除时间未定义,旧数据行"创建时间"不变,删除时间=该事务的DB_ROW_ID。DELETE操作,相应数据行的"创建时间"不变,删除时间=该事务的DB_ROW_ID;

小结

本篇重点介绍了MySQL的各种锁机制,熟练其概念及在开发中正确使用是很有必要的。
我是少侠露飞,爱技术,爱分享。
祝大家国庆快乐!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值