Mysql锁

文章目录

  • [1. MySQL并发事务访问相同记录](#1. MySQL并发事务访问相同记录)
  • [2. 锁的分类](#2. 锁的分类)
    • [2.1 共享锁和排他锁](#2.1 共享锁和排他锁)
    • [2.2 表锁、行锁、页锁](#2.2 表锁、行锁、页锁)
      • [2.2.1 表锁(Table Lock)](#2.2.1 表锁(Table Lock))
      • [2.2.2 行锁](#2.2.2 行锁)
    • [2.3 乐观锁、悲观锁](#2.3 乐观锁、悲观锁)
    • [2.4 按加锁的方式分:显示锁、隐式锁](#2.4 按加锁的方式分:显示锁、隐式锁)
    • 2.5全局锁和死锁
  • [3. 锁的内存结构](#3. 锁的内存结构)

事务的隔离性由锁来实现

1. MySQL并发事务访问相同记录

读-读: ok

写-写: 脏写问题,一个事务已经commit了但是发现没写进去。--->排队执行is_waiting

读-写、写-读问题:这种情况下可能发生 脏读不可重复读幻读 的问题。

并发问题的解决方案:

  • 方案一:操作利用多版本并发控制( MVCC),操作进行 加锁

    普通的SELECT语句在READ COMMITTEDREPEATABLE READ隔离级别下会使用到MVCC读取记录。

    在 READ COMMITTED 隔离级别下,一个事务在执行过程中每次执行SELECT操作时都会生成一个ReadView,ReadView的存在本身就保证了 事务不可以读取到未提交的事务所做的更改 ,也就 是避免了脏读现象;

    REPEATABLE READ 隔离级别下,一个事务在执行过程中只有 第一次执行SELECT操作 才会生成一个ReadView,之后的SELECT操作都 复用 这个ReadView,这样也就避免了不可重复读 和幻读的问题

  • 方案二:读、写操作都采用加锁的方式

    问题:可以解决脏读、不可重复读,幻读的话---不知道

2. 锁的分类

2.1 共享锁和排他锁

共享锁:S锁。读操作可以同时进行而不会互相影响,相互不阻塞的。SELECT ... FOR share

排他锁:X锁。当前写操作没有完成前,它会阻断其他写锁和读锁。SELECT ... FOR UPDATE

对于InnoDB引擎来说,读锁和写锁可以加在表上,也可以加在行上。


写操作:

  • DELETE:
    对一条记录做DELETE操作的过程其实是先在B+树中定位到这条记录的位置,然后获取这条记录的X锁,再执行delete mark.操作(标记一下)。也可以把这个定位待删除记录在B+树中位置的过程看成是一个获取X锁的锁定读。
  • UPDATE︰在对一条记录做UPDATE操作时分为三种情况:
    情况1: 未修改该记录的键值,并且被更新的列占用的存储空间在修改前后未发生变化。 则先在B+树中定位到这条记录的位置,然后再获取一下记录的X锁,最后在原记录的位置进行修改操作。也可以把这个定位待修改记录在B+树中位置的过程看成是一个获取X锁的锁定读。
    情况2∶未修改该记录的键值,并且至少有一个被更新的列占用的存储空间在修改前后发生变化。 则先在B+树中定位到这条记录的位置,然后获取一下记录的X锁,将该记录彻底删除掉(就是把记录彻底移入垃圾链表),最后再插入一条新记录。这个定位待修改记录在B+树中位置的过程看成是一个获取X锁的锁定读,新插入的记录由INSERT
    操作提供的隐式锁进行保护。
    情况3∶ 修改了该记录的键值,则相当于在原记录上做DELETE操作之后再来一次INSERT操作,加锁操作就需要按照DELETE和INSERT的规则进行了。
  • INSERT :
    一般情况下,新插入一条记录的操作并不加锁,通过一种称之为隐式锁的结构来保护这条新插入的记录在本事务提交前不被别的事务访问。

2.2 表锁、行锁、页锁

2.2.1 表锁(Table Lock)

会锁定整张表,是开销最小的策略(因为粒度比较大)。可以很好的避免死锁问题。并发性能差。

MyIsam默认是表锁,InnoDB默认是行锁

  1. 表级 S、X锁

  2. 意向锁 :它允许 行级锁 与 表级锁 共存,即意向锁和行级锁兼容(我认为是一种行级锁标志

    • 意向锁是一种不与行级锁冲突表级锁,这一点非常重要。
    • 表明"某个事务正在某些行持有了锁或该事务准备去持有锁"

如果我们给某一行加上了排它锁,InnoDB会向表级别加上意向锁,告诉其他人这个表已经有人上过排它锁了,这样当其他人想要获取表级别锁的时候,只需要了解是否有人已经获取了这个表的意向排他锁即可。原来需要一条记录一条查,看看行有没有X锁,现在不用了,在表的意向排他锁看一下就ok

口述:意向锁其实就像一种行级锁的标志,当我们要加表锁时,如果表中有行的X锁的话,那其实是不能让这个事务获得表锁的,这样的话如果没有意向锁的话就要去一条一条记录去查看看有没有X锁,这样的话就性能低,意向锁就是在表级别上的这样一个锁,它描述的就是表中记录的X锁啊S锁啊这种,其实更类似于一种行锁的标记吧,也是为了快速判断能不能加表锁。

  1. 自增锁(AUTO-INC锁):id字段声明了AUTO_INCREMENT,意味着在书写插入语句时不需要为其赋值
  2. 元数据锁(MDL锁):为了保证DDL与DML语句的正确性的,比如在DML时候,会加MDL读锁,在DDL时,会加MDL写锁,读写是互斥的,保证了数据正确性。比如:先查出数据,其次对表结构更改了,然后再查出数据,这肯定是不可以的,表结构都改了查出来也肯定不一样。

2.2.2 行锁

  1. 记录锁:S锁和X锁

  2. 间隙锁:以锁的方式解决了幻读,事务在第一次执行读取操作时,那些幻影记录尚不存在,我们无法给这些 幻影记录 加上 记录锁 ,间隙锁可以。

  3. 临键锁:innodb默认的锁就是临键锁,它其实是记录锁和间隙锁的一种结合】

    在3-8之间加间隙锁,id为8的记录加记录X锁。

  4. 插入意向锁:插入意向锁是一种 特殊的Gap锁 ,在insert操作时产生。

    插入意向锁其实是在插入时,被间隙锁阻塞,这时候事务会持有这个插入意向锁,锁结构里面is_waiting为true,等待持有间隙锁的事务释放了锁才能insert。

2.3 乐观锁、悲观锁

  • 乐观锁:update version字段,条件是version
  • 悲观锁:会通过数据库自身的锁机制来实现,从而保证数据操作的排它性。Java中 synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现。
    select ... for update语句执行过程中所有扫描的行都会被锁上,因此在MySQL中用悲观锁必须确定使用了索引,而不是全表扫描,否则将会把整个表锁住

2.4 按加锁的方式分:显示锁、隐式锁

口述:隐式锁:其实这种锁结构的话我们查是查不到的,所以叫它为隐式锁,比如说我们执行insert时,它其实是不会加锁的,当然只是针对隐式锁,这种写操作的话是会加X锁的,那针对隐式锁,当insert时候别的事务没有访问这条记录,那自然不会产生冲突,也就没有这种隐士锁,那当insert时别的事务也要访问这条记录,这时候其实这些访问的事务会为原来的事务加一个隐式锁,吧insert的事务的is_waiting设置为false,自己的设计为true,然后等待,其实它是判断了insert这条记录的隐藏列中的事务id,一看别的事务加了就为原来事务生成个隐式锁,自己的锁结构设置为等待状态,从而避免了脏写和脏读。

显示锁:前面的----------略

2.5全局锁和死锁

全局锁:在库的层面加锁

死锁:行锁可能导致死锁。

死锁:

如何处理死锁:

  • 方式1:等待,直到超时(innodb_lock_wait_timeout=50s)这种方式不太好,我们设计锁的目的就是为了避免同时进入,让一些事务等待,设置超时时间的话粒度不好控制
  • 方式2:使用死锁检测处理死锁程序(检测有没有环)
    死锁检测的原理是构建一个以事务为顶点,锁为边的有向图,判断有向图是否存在环,存在既有死锁。

3. 锁的内存结构

相关推荐
瓜牛_gn2 小时前
mysql特性
数据库·mysql
Yaml47 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
追风林7 小时前
mac 本地docker-mysql主从复制部署
mysql·macos·docker
Hsu_kk9 小时前
MySQL 批量删除海量数据的几种方法
数据库·mysql
编程学无止境9 小时前
第02章 MySQL环境搭建
数据库·mysql
knight-n9 小时前
MYSQL库的操作
数据库·mysql
eternal__day11 小时前
MySQL_聚合函数&分组查询
数据库·mysql
咕哧普拉啦12 小时前
乐尚代驾十订单支付seata、rabbitmq异步消息、redisson延迟队列
java·spring boot·mysql·spring·maven·乐尚代驾·java最新项目
春哥的魔法书13 小时前
数据库基础(5) . DCL
数据库·mysql
鬼才血脉13 小时前
docker+mysql配置
mysql·adb·docker