表锁
Myisam存储引擎只支持表锁
InnoDB存储引擎支持表锁和行级锁
# 表共享锁
lock table XXX read
# 表独占锁
lock table XXX write
行级锁
InnoDB存储引擎支持表锁和行级锁
select .... lock in share mode;
select .... for update;
insert update delete
排他锁(行锁)
行级锁之一,一个事务获取了一行数据的排他锁,其他事务不能获取该行数据的排他锁与共享锁,即阻塞其他用户对同一数据的当前读和写操作
共享锁(行锁)
行级锁之一,一个事务获取了一行数据的共享锁,其他事务不能获取该行数据的排他锁,但可以获取共享锁,即只阻塞其他用户对同一数据的写操作
意向排他锁(表锁)
意向锁是表锁,意向锁之间不冲突,但是意向锁和表锁及行锁冲突,所以意向排他锁和排他锁冲突,意向排他锁和共享锁也冲突
意向共享锁(表锁)
意向锁是表锁,意向锁之间不冲突,但是意向锁和表锁及行锁冲突,所以意向共享锁和排他锁冲突
间隙锁
InnoDB范围查询会产生间隙锁,左开右闭,保证数据范围内锁定
next-key锁
InnoDB RR模式下,非唯一索引的查询,或唯一索引查询未命中,或唯一索引查询到多条数据,都会产生next-key锁,即行级锁 + gap间隙锁
- where唯一索引,命中,行锁
- where唯一索引,未命中,间隙锁
- where多个唯一索引,命中,临键锁
- where普通索引范围,临键锁
- where普通索引,命中,临键锁
- where普通索引,未命中,间隙锁
- where非索引,会在主键上使用间隙锁,等于锁住全表,开销极大
插入意向锁(特殊的间隙锁)
插入意向锁是一种间隙锁形式的意向锁,意向锁之间不冲突,但是跟真正的间隙锁冲突,所以RR级别下,next-key锁配合插入意向锁可以实现杜绝幻读。 INSERT 语句在执行插入之前,会先在 gap 中加入插入意向锁,如果是唯一索引,还会进行 Duplicate Key 判断,如果存在相同 Key 且该 Key 被加了互斥锁,则还会加共享锁,然后等待(因为这个相同的 Key 之后有可能会回滚删除,这里非常容易死锁)。等到成功插入后,会在这条记录上加排他记录锁
auto-inc锁(表锁)
自增锁是一种特殊的表级锁。专门针对事务插入AUTO_INCREMENT类型的列 如果表中存在自增键,insert语句未指定值,则多个事物的插入操作将产生排队阻塞.
乐观锁
乐观锁是一种思想,而不是真正的锁,总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,只在更新的时候会判断一下在此期间别人有没有去更新这个数据。 一般来讲是通过版本号实现。
乐观锁和MVCC区别
乐观锁主要解决写写冲突,MVCC主要解决读写冲突
悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞,直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中
synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。 死锁的产生:
- 行级锁互相阻塞,比如A事物获取a,b两条数据的排他锁,B事物获取数据b,a两条数据的排他锁,A和B可能就会产生相互阻塞等待
- 产生间隙锁后,在间隙中执行insert操作,此时产生插入意向锁防止幻读,这个操作本身没问题,但是如果有两个事物同时执行该逻辑,A和B各自都获取了间隙锁(间隙锁可以共存),A想执行insert但是插入意向锁和B的间隙锁冲突,B想执行insert但是和A的间隙锁冲突,互相阻塞,产生死锁 这里的参考文章: blog.csdn.net/fofcn/artic…
INNODB的锁机制
InnoDB支持事物,有四种事物隔离级别.其中RC,RR级别又分为当前读和快照读.
- 当前读按照上文所说的封锁协议 + next-key锁实现
- 快照度通过MVCC机制实现
当前读-悲观锁实现
一般数据库的四种隔离级别
- 未提交读 RU
- 提交读(不可重复读) RC
- 可重复读 RR
- 序列化 Serializable
为了支持这4种隔离级别,一般可以通过三级封锁协议规范实现。
封锁协议:
- 一级封锁协议: 读不加锁,写加排他锁,事务结束释放 (RU)
- 二级封锁协议: 读加共享锁,读完就释放,写加排他锁,事务结束释放 (RC,解决脏读)
- 三级封锁协议:读加共享锁,事务结束释放,写加排他锁,事务结束释放 (RR,解决脏读,不可重复读,但是存在幻读)
MYSQL的RR当前读不仅使用三级封锁协议,还加入了next-key锁,配合插入意向锁,防止幻读产生,但也使得开销更大,更容易发生死锁,推荐使用RC隔离级别(其他数据库默认都是RC,只有MYSQL默认RR,这是MYSQL的binLog日志格式报错的历史问题导致的),至于RC产生的不可重复读问题,很多情况反而契合真实业务,需要具体分析.
快照读-MVCC
各个数据库的多版本并发控制实现并不相同.MYSQL的MVCC通过隐藏字段,undo-log,readview实现
-
数据行隐藏字段:每个数据行都有4个隐藏字段
- DB_TRX_ID最近操作的事务ID
- DB_ROLL_PTR指向上个版本的回滚指针,rollback时使用
- DB_ROW_ID隐式自增主键,如果数据没有主键,会生成一个这样的隐藏主键
- DELETED_BIT,删除标记位,删除时设置
-
undo-log 回滚日志,rollback时使用,实现事务的原子性
- undolog是逻辑日志,记录相反的操作SQL,比如事务中delete,undolog中就记录insert
- 在更新数据之前,MySQL会提前生成undo log日志,当事务提交的时候,并不会立即删除undo log,因为后面可能需要进行回滚操作,要执行回滚(rollback)操作时,从缓存中读取数据。undo log日志的删除是通过通过后台purge线程进行回收处理的。
- 大概的链路图(网上找的)
-
readView是一个结构体,主要存储了4个字段
- m_ids 表示在生成readview时,当前系统中活跃的读写事务id列表
- min_trx_id: 表示在生成readview时,当前系统中活跃的读写事务中最小的事务id,也就是m_ids中最小的值
- max_trx_id: 表示生成readview时,系统中应该分配给下一个事务的id值
- creator_trx_id: 表示生成该readview的事务的事务id
RC模式下: 每次快照读select语句生成一个readView,select完成生命周期即结束
RR模式下: 每次事物的第一个select生成readView,事务结束时生命周期才结束