锁粒度角度

行锁

每次操作锁住一条行记录,数据库在能够确定哪些行记录需要锁的情况下会使用行锁,如果不知道会影响哪些行记录的时候就会使用表锁。

行锁的开销大,锁粒度小,加锁慢,发生锁冲突的概率较低,并发程度高。

页锁

操作时在页的粒度上进行锁定,由于一页上会有多个行,所以使用页锁会出现数据浪费。

页锁的开销介于行锁和表锁,会出现死锁。并发程度一般。

表锁

每次操作锁住整张表。

表锁的开销小,加锁快,发生锁冲突的概率最高,并发程度最低。

不同的数据库引擎可以使用的锁类型不同,每个层级锁数量有限,因为锁会占用内存空间,锁空间大小有限。当锁数量超过该层级阈值,会进行锁升级,用更大粒度的锁替代多个小粒度的锁。随着锁空间的下降,并发程度也会下降。

image.png

管理角度

共享锁(读锁、S 锁)

共享锁锁定的数据可以被其他用户读取,但是不能修改(只读模式)。若事务 T 对数据对象 A 加上 S 锁,则事务 T 只能读 A;其他事务只能再对 A 加 S 锁,而不能加 X 锁,直到 T 释放 A 上的 S 锁。这就保证了其他事务可以读 A,但在 T 释放 A 上的 S 锁之前不能对 A 做任何修改。

排他锁(写锁、X 锁)

排他锁锁定的数据只允许加锁的事务使用,其他事务无法对该数据进行修改和查询。若事务 T 对数据对象 A 加上 X 锁,则只允许 T 读取和修改 A。其他任何事务都不能再对 A 加任何类型的锁,直到 T 释放 A 上的锁。这就保证了其他事务在 T 释放 A 上的锁之前不能再读取和修改 A。

意向锁

当数据被加上锁后,数据库会在更高一层的空间里加上意向锁,告诉其他事务该数据页或者数据表上含有锁。当事务获取某些记录的共享锁或者排他锁时,数据标上会添加上意向共享锁或者是意向排他锁。

死锁

多个事务(进程)在执行过程中,因为竞争某个相同的资源而造成阻塞称为死锁。当多个事务对同一个数据获得读锁时,可能就会发生死锁。例如两个不同的客户端都获取了某数据的共享锁,这时所有的客户端都无法对该数据进行更新,因为共享锁会阻止其他事务对数据更新。如果一个客户端进行更新,就会出现死锁。死锁解决的需要一个事务进行回滚,让另外一个事务获取锁完成事务,然后将锁释放。

并发角度

乐观锁

乐观锁认为对同一数据的并发操作不会经常发生,属于小概率事件。所以不用每次都对数据上锁,也不采用数据库本身的锁机制,而是通过程序来实现。可以使用版本号、时间戳和 CAS 算法实现。

版本号机制

在数据表中设置一个版本字段,表示数据被修改的次数。每次数据被修改时,version 值会加一。当线程对数据进行更新或者删除时,会执行UPDATE ... SET version=version+1 WHERE version=version,在提交更新时只有 version 值和当前数据库中的 version 值相等才会更新,如果有事务对这条数据进行了其他修改则重试更新操作,直到更新成功。

时间戳机制

时间戳和版本号机制一样,在提交更新时,将当前数据的时间戳和更新之前的时间戳进行比较,如果两者相同更新成功,否则版本冲突。

CAS 算法

CAS 即 compare and swap(比较并交换),是一种有名的无锁算法,在不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步。CAS 算法涉及到三个操作数:

  • 要更新的变量 V
  • 预期的值 E
  • 新值 N

仅当 V 值等于 E 值时,才会将 V 的值设置成 N,否则什么都不做。最后 CAS 返回当前 V 的值。CAS 算法需要你额外给出一个期望值,也就是你认为现在变量应该是什么样子,如果变量不是你想象的那样,就说明已经被别人修改过,就重新读取,再次尝试修改即可。

因为 CAS 需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但如果一个值原来是 A,变成了 B,又变成了 A,那么使用 CAS 进行检查时就会误以为它的值没有发生变化,这个问题称为 ABA 问题。ABA 问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么 A-B-A 就会变成 1A-2B-3A,以此来防止不恰当的写入。

悲观锁

悲观锁假设最坏的情况,担心每次拿的数据都会被其他的事务修改,所以每次都会在取数据的时候加锁。别的事务想要修改或者拿到数据就会被阻塞直到它拿到锁。

行锁表锁共享锁排他锁都属于常用的悲观锁。

两个锁的适用场景

  1. 乐观锁适用于读操作多的场景,优点在于通过时间戳或者版本号来判断,节省了加锁的开销,不存在死锁的问题,系统的吞吐量也得到了提升。
  2. 悲观锁适用于写操作多的场景,写操作具有排他性,悲观锁可以在数据库层面组织其他的事务对数据的操作,防止读-写、写-写的冲突发生。

参考

  1. 数据库锁分类和总结
  2. CS-NOTE 数据库
  3. 面试必备之乐观锁与悲观锁
  4. 并发策略-CAS 算法