欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

[MySQL]InnoDB锁详解

时间:2023-07-04

除非有什么特别的原因必须使用其他储存引擎, 不然 MySQL 就应该优先考虑 InnoDB

锁的分类

按照锁的细粒度分为

行级锁页级锁(分段锁)表级锁 乐观锁和悲观锁

锁的分类按照使用方式分为乐观锁和悲观锁, 乐观锁和悲观锁是 2 钟思想, 跟编程语言和数据库没有关系

乐观锁: 乐观锁不会上锁, 只是在数据更新的时候数据是否被并发改变, 如果改变了就放弃操作悲观锁: 悲观锁就是一般的锁, 会上锁, 上锁期间其他人不能修改数据

乐观锁主要有 2 种实现方式, CAS 和 版本号

1、CAS 实现乐观锁

CAS 是 Compare And Swap, 原子性的执行操作: 检查内存中的值是否符合预期, 符合就执行修改(swap), 不符合就操作失败(一般的乐观锁的实现都会遍历多次 CAS 直到操作成功)

golang atomic 包下的 CAS 函数

// CompareAndSwapInt32 executes the compare-and-swap operation for an int32 value.func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)

golang Mutex 互斥锁就是用 CAS 获取锁的

func (m *Mutex) lockSlow() {var waitStartTime int64starving := falseawoke := falseiter := 0old := m.state//慢速模式进来就是一个大的 for 循环for { ...// 3、尝试 atomic 的 compareAndSwap 函数更新状态, 锁是通过 atomic 获取的.if atomic.CompareAndSwapInt32(&m.state, old, new) {//(1) 如果通过原子 atomic 操作获取锁成功,就结束循环if old&(mutexLocked|mutexStarving) == 0 {break // locked the mutex with CAS 获取锁就退出}// If we were already waiting before, queue at the front of the queue.queueLifo := waitStartTime != 0if waitStartTime == 0 {waitStartTime = runtime_nanotime()}//(2) atomic 获取锁失败就在队列中等待, runtime 的 runtime_SemacquireMutex() 等待的方法可以通过信号量通信.runtime_SemacquireMutex(&m.sema, queueLifo, 1)//(3) 还会通过 staving 时间更新互斥锁的 staving 状态,如果当前队列只有该线程, starving 状态退出starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNsold = m.state//(4) 处理些边角情况 ...}

2、CAS 如何保证原子性?

CAS 是 CPU 支持的原子性操作, 硬件层面保证原子性.

3、CAS 有什么缺点

ABA 问题:

内存中的值被并发修改但是最后和预期一样, CAS 就会成功, 但是这并不是原子性

一般的情况没啥问题, 因为反正值就是在预期的, 比如 golang 的 Mutex 就是这样实现的

有些情况比如某些操作只能做一次就不行, 举个例子就是栈顶问题

解决方法就是进入版本号双重控制

高竞争开销:

CAS 失败后一般会不断尝试直到成功(比如加锁), 所以在竞争很高的情况下,比如 100 个并发竞争修改一个, 99 个都在不断尝试获取锁又不休眠, 开销可想而知

解决方法就是用悲观锁(高竞争就是用悲观锁, 低竞争采用乐观锁)

功能限制:

CAS 只支持单个变量的 CAS

4、版本号实现乐观锁

思路就是在数据中增加要给字段 version , 每次更新前面对比下版本号是否一致, 更新后版本号 +1

只有版本一致才修改成功, 版本不一致就放弃修改

很多数据库表都有这种设计, spring 的 mybatis-plus ORM 框架能直接通过 @version 注解到字段上实现这个功能

5、什么是否使用乐观锁

竞争小的时候使用乐观锁, 竞争大的时候使用悲观锁

InnoDB 中加锁 1、InnoDB 锁类型

共享锁/读锁: 行锁排他锁/写锁: 行锁意向共享锁: 表锁, 内部调用意向排他锁: 表锁, 内部调用 2、使用方式

开发者能使用的锁只有共享锁和排他锁, 因为意向锁值 InnoDB 内部调用的表锁, 不支持开发者使用

共享锁: 读锁, 只读, 允许并发读排他锁: 写锁, 锁定之后只能该实例操作, 不能并发读写

InnoDB 我们在 update 和 delete 的时候默认就是 排他锁, 一般加锁都是在 select 的时候

# 共享锁select name from user where id = 1 lock in share mode;# 排他锁select name from user where id = 1 for update;

其实我们开发更加常用加乐观锁方式, 就是表中增加的 version 字段做版本控制的乐观锁, 很多框架都有乐观锁的支持,比如 mybatis-plus 通过 @version 注解就能从框架层实现乐观锁

3、分析数据库的锁的性能

记录慢SQL来分析锁的性能, 我们也能直接查询到 MySQL 自己统计的 metric 数据

mysql> show status like 'innodb_row_lock%';+-------------------------------+-------+| Variable_name | Value |+-------------------------------+-------+| Innodb_row_lock_current_waits | 0 || Innodb_row_lock_time | 2 || Innodb_row_lock_time_avg | 0 || Innodb_row_lock_time_max | 0 || Innodb_row_lock_waits | 6 |+-------------------------------+-------+5 rows in set (0.04 sec)mysql>

4、InnoDB 什么时候表锁

InnoDB 的行锁是基于索引来实现的如果条件没有用到索引或者索引失效将导致表锁(应该尽力避免!!)

比如下面 name 没有在索引里将会导致表锁

update user set name = '张三' where name = '李四';

refrence

MySQL InnoDB引擎锁的总结 - SegmentFault 思否

【BAT面试题系列】面试官:你了解乐观锁和悲观锁吗? - 编程迷思 - 博客园 (cnblogs.com)

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。