mysql-锁
锁
锁是计算机协调多个进程或线程并发访问某一资源的机制,为保证数据的一致性,需要对并发操作进行控制,因此产生了锁。同时锁机制也为mysql各个隔离级别提供了保证,锁冲突也是影响数据库并发性能的一个重要因素。
并发事务访问
- 读-读情况:并发事务相继读取相同的记录,读取操作本事并不会对记录有任何影响,不会引起问题。
- 写-写情况:并发事务相继对相同记录做出改动,可能发生脏写问题,在多个未提交事务相继对一条记录做改动时候,需要它们排队执行,排队的过程实际上锁实现的。所谓的锁实际是一个内存中的结构,一开始没有锁结构和记录进行关联,当一个事务想对这条记录做改动时,首先看内存中有没有与这条记录关联的锁机构,如果没有就会在在内存中生成一个锁结构与之关联。
- 不加锁: 不需要在内存中生成对应的锁结构,可以直接执行操作
- 加锁成功: 在内存中生成了对应的锁结构,is_waiting 属性为FALSE,事务可以继续执行操作。
- 加锁失败:在内存中生成了对应的锁结构,is_waiting 属性为true,事务需要等待,不可以继续执行操作。
- 读-写、写-读情况:一个事务执行读取操作,另一个事务进行改动操作。 可能发生脏读、不可重复读、幻读
并发问题的解决方案:怎么解决脏读、幻读、不可重复读这些问题?
- 读操作利用MVCC, 写操作进行加锁,所谓的MVCC就是生成一个readview,通过readview找到符合条件的记录版本(历史版本由undo log构建),查询语句只能读到在生成readview之前已经提交的事务,在生成readview之前未提交的事务或者之后才开启的事务所做的更改是看不到,而写操作肯定针对的是最新版本的记录,读写操作并不冲突。
普通的select语句在RC 和RR 隔离级别下会使用到MVCC读取记录;
在RC隔离级别下,一个事务在执行过程中每次执行select操作都会生成一个readview,保证了事务不可以读取到未提交的事务所做的更改,避免了脏读现象
在RR隔离级别下,一个事务在执行过程只有第一次执行select语句会生成一个readview,之后的select语句都复用这个readview,避免了不可重复读以及幻读的现象。 - 读写操作均采用加锁,如果业务场景不允许读取记录的旧版本,每次都必须读取记录的新版本,读取记录就需要加锁操作
幻读问题的产生是因为当前事务读取了一个范围的记录,然后另外的事务向该范围插入了新纪录,当前事务再次读取到了新插入的记录,读取的时候加锁有麻烦,不知道给谁加锁。
锁的分类
操作类型:读锁、写锁
- 读锁:也称为共享锁,针对同一份数据,多个事务的读操作可以同时进行而不会相互影响阻塞
- 写锁:也称为排他锁,当前写操作没有完成前,会阻断其他写锁和读锁,在给定的时间内,只有一个事务能执行写入,防止其他用户读取正在写入的同一资源。
操作粒度:表锁、页锁、行锁
- 表级别的S锁、X锁 : 这个过程其实同在server层使用一种称之为元数据锁(Metadata Locks)的结构实现的。 MyISAM在执行select语句前,会给设计的所有表加读锁,在执行增删改操作前,给涉及的表加写锁,InnoDB是不会的。
- 意向锁: 意向共享锁,事务有意向对表中的某些行加共享锁;意向排他锁,事务有意向对表中的某些行加排他锁。意向锁是由存储引擎自己维护的,用户无法手动操作意向锁,在为数据行加共享排他锁之前,会先获取该数据的行所在数据表的对应意向锁。意向锁就是为了给更大一级别的空间示意里面是否已经上过锁。
- 自增锁: 是当向使用含有自增列的表中插入数据时需要获取的一种特殊的表级锁,一个事务在持有自增锁的过程中,其它事务的插入语句都要被阻塞,保证一个语句中的分配的递增值是连续的。
- 元数据锁:保证读写的正确性,当对一个表做增删改查操作时候,加MDL锁;当对表做结构变更操作时候,加MDL写锁。
- 记录锁:仅仅把一条记录上锁。
- 间隙锁: mysql在RR隔离级别下解决幻读,一种是MVCC,一种是加锁。例在ID为8的记录上加一个gap锁,以为着不允许别的事务在ID为8的记录的间隙插入新记录。gap锁的提出仅仅是为了防止插入幻影记录而提出。
- 临键锁: 既想锁住某条记录,又想阻止其他事务在该记录前边的间隙插入新纪录,添加Next-Key Locks ,在事务级别为RR使用的数据库锁。
- 插入意向锁:插入意向锁是一种gap锁,在插入一条记录前,由insert操作产生的一种间隙锁。
- 页锁:页锁的开销结余表锁和行锁之间,会出现死锁现象。
对待态度: 乐观锁、悲观锁
- 乐观锁:使用于读操作多的场景,不采用数据库自身的锁机制,通过程序实现。 版本号机制或者时间戳机制
- 悲观锁:使用于写操作多的场景,
加锁方式: 显示锁、隐式锁
- 隐式锁:即一个事务对新插入的记录可以不显示的加锁,但是由于事务ID的存在,相当于加锁
- 显示锁:通过特定的语句进行加锁,称为显示锁。
select ... lock in share mode (for update)
锁的内存结构
对一条记录加锁本质上就是在内存中创建一个锁结构与之关联,在决定对不同记录加锁时候,如果符合以下记录会放到一个锁结构中,
在同一个事务中进行加锁操作,被加锁的记录在同一个页面中,加锁的类型是一样的,等待状态是一样的。
锁所在事务信息 | 索引信息 | 表锁、行锁信息 | type—mode | 其他信息 | 一堆比特位 |
---|---|---|---|---|---|
在内存结构中是一个指针,通过指针找到更多信息 | 对于行锁需要记录加锁的记录属于哪个索引 | 表锁记录和行锁不同 | 锁的模式 、类型、行锁的具体类型 | 为了更好管理系统运行过程中生成的各种锁结构而设计的各自hash表和链表 | 行锁结构的话,末尾放置了一堆比特位 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了