锁机制2| 青训营笔记

110 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 13 天

锁机制的底层实现:

Java中,synchronized由monitor实现,ReentranLock由AQS实现。Mysql的锁呢?

内存结构:

锁的事务信息:

一个指针,表示那个事务产生的锁,指向具体事务

索引信息:

一个指针,这个是行锁特有的,表示行数据属于的索引,那个节点

锁粒度信息:

对于表锁:记录属于那张表,以及表的一些其他信息

对于行锁:记录很多信息,下面三个最重要:

  • Space ID :表空间ID
  • Page Number:行数据的,页号
  • n_bits:在一页中表示那个行上锁了。

锁类型信息:

采用一个32bit的type_mode来表示这个32bit的值可以拆为lock_mode、lock_type、rec_lock_type三部分,如下:

lock_mode:表示锁的模式,使用低四位。

  • 0000/0:表示当前锁结构是共享意向锁,即IS锁。
  • 0001/1:表示当前锁结构是排他意向锁,即IX锁。
  • 0010/2:表示当前锁结构是共享锁,即S锁。
  • 0011/3:表示当前锁结构是排他锁,即X锁。
  • 0100/4:表示当前锁结构是自增锁,即AUTO-INC锁。

lock_type:表示锁的类型,使用低位中的5~8位。

  • LOCK_TABLE:当第5个比特位是1时,表示目前是表级锁。
  • LOCK_REC:当第6个比特位是1时,表示目前是行级锁。

rec_lock_type:表示行锁的具体类型,使用其余位。

  • LOCK_ORDINARY:当高23位全零时,表示目前是临键锁。
  • LOCK_GAP:当第10位是1时,表示目前是间隙锁。
  • LOCK_REC_NOT_GAP:当第11位是1时,表示目前是记录锁。
  • LOCK_INSERT_INTENTION:当第12位是1时,表示目前是插入意向锁。
  • .....:内部还有一些其他的锁类型,会使用其他位。

is_waiting:表示目前锁处于等待状态还是持有状态,使用低位中的第9位。

  • 0:表示is_waiting=false,即当前锁无需阻塞等待,是持有状态。
  • 1:表示is_waiting=true,即当前锁需要阻塞,是等待状态。

其他信息:

辅助锁机制的信息,比如之前死锁检测机制中的「事务等待链表、锁的信息链表」,每一个事务和锁的持有、等待关系,都会在这里存储,将所有的事务、锁连接起来,就形成了上述的两个链表。

锁的比特位:

表示行数据那些为被上锁了。通过一个比特数组进行标识。

锁使用:

InnoDB的锁实现:

  • ①目前对表中不同行记录加锁的事务是同一个。
  • ②需要加锁的记录在同一个页面中。
  • ③目前事务加锁的类型都是相同的。
  • ④锁的等待状态也是相同的。

当上述四点条件被满足时,符合条件的行记录会被放入到同一个锁结构中。mysql不用为每一个行数据上一个锁。

获取锁的过程

当一个事务需要获取某个行锁时,首先会看一下内存中是否存在这条数据的锁结构,如果存在则生成一个锁结构,将其is_waiting对应的比特位改为1,表示目前事务在阻塞等待获取该锁,当其他事务释放锁后,会唤醒当前阻塞的事务,然后会将其is_waiting改为0,接着执行SQL。

事务隔离的底层实现:

在最基本的并发里面,写写操作会导致写覆盖的场景,这种是RU(读未提交)不能够允许的。

RU读未提交的实现:

通过对写操作加上排他锁,事务1在修改数据的时候,事务2不能够进行修改,只能等待。

RC读已提交:

通过对读操作上共享锁(允许读取操作共享),事务2在读取被事务1上了排他锁的数据的时候会被排斥,事务2需要等待事务1结束后才能读数据。

但是1,这种方式使得事务串行化了,于是通过MVCC机制使得能够查询的时候获取快照,然后根据快照选择一个可读的数据版本。

但是2,事务1提交数据后,事务2的查询每次都会生成一个ReadView,就能读取到数据,就会导致不可重复读的情况

RR可重复度:

通过两种方案解决不可重复读的问题:

  • 查询时,通过加上临键锁(就是行锁+间隙锁),即不允许其他事务改动
  • MVCC机制优化,一个事务只能生成一次ReadView。即首次生成快照,这样也不会出现幻读额外难题。

序列化——MVCC又没有彻底解决幻读问题:

要想彻底解决幻读问题,还是需要序列化。

序列化只能够读读并发。

死锁:

如果有一张表主键建立了索引,对于字段中的AB,事务一先对A操作再对B操作。事务二在事务一操作了A后对B进行操作,这是就产生了死锁。注意需要表中对这个字段加上索引,否则innodb中没有命中一条数据,那么回对全表进行上锁而不是锁一条数据。

死锁解决方案:

方案一:超时机制

事务和线程在等待锁时,超过一定时间自动放弃等待并返回。这种方式不好控制,如果设置时间过程势必会导致大量线程进行阻塞,如果设置时间太短又会势必造成误伤友军。

方案二:死锁检测算法-wait for graph

这种方式通过判断是否形成循环等待链条的方式判断是否又死锁生成。算法手机两个信息:

  • 锁的信息链表:目前持有每个锁的事务是谁。
  • 事务等待链表:阻塞的事务要等待的锁是谁。

死锁的避免:

  • 合理索引结构
  • 降低锁的限制
  • sql执行顺序
  • 拆分业务粒度
  • 不要手动获取排他锁
  • 。。。。。