01.mysql中的锁

1.1 锁分类

  • 按使用方式划分:乐观锁、悲观锁

  • 按锁级别划分:共享锁、排他锁

  • 按锁的粒度划分:表级锁、行级锁、页级锁

  • 按操作划分:DML锁,DDL锁

  • 按加锁方式划分:自动锁、显示锁

1.2 共享锁和排它锁

  • 共享锁(读锁

    • 共享锁又叫读锁,如果事务T对A加上共享锁,则其它事务只能对A再加共享锁,不能加其它锁。
    • 获准共享锁的事务只能读数据,不能写数据。
    • 用法: SELECT … LOCK IN SHARE MODE;
  • 排它锁(写锁

    • 排它锁又叫写锁,如果事务T对A加上排它锁,则其它事务都不能对A加任何类型的锁。获准排它锁的事务既能读数据,又能写数据。
    • 用法 : SELECT … FOR UPDATE

1.3 乐观锁和悲观锁

2.3.1 悲观锁:写少读多

悲观锁(Pessimistic Lock)

  • 定义:
    悲观锁假设会发生冲突,因此在读取数据时就加锁,直到操作完成才释放锁,确保只有一个事务能够对数据进行操作。悲观锁会直接阻塞其他事务的访问,直到当前事务完成。

  • 实现方式:

    1.使用数据库的锁机制,例如 SELECT … FOR UPDATE 来锁定数据行。
    2.在事务开始时,锁定目标记录,其他事务必须等待当前事务提交或回滚后才能操作被锁定的数据。

  • 使用场景:
    1.高并发系统: 当并发较高时,使用悲观锁可以避免数据冲突,通过加锁保证事务的顺序性。
    2.关键性的数据更新: 例如库存扣减等操作,确保数据的一致性和准确性。
    3.长时间操作的事务: 当事务需要进行复杂的操作且无法快速完成时,使用悲观锁可以防止其他事务对数据的更改,避免脏读或丢失更新。

1
2
3
4
5
6
7
8
#### 悲观锁实现加一操作代码
# 我们可以看到,首先通过begin开启一个事物,在获得shop信息和修改数据的整个过程中都对数据加锁,保证了数据的一致性。
'''
begin;
select id,name,stock as old_stock from shop where id=1 for update;
update shop set stock=stock-1 where id=1 and stock=old_stock;
commit
'''

2.3.2 乐观锁:读多写少

乐观锁(Optimistic Lock)

  • 定义:
    乐观锁假设不会发生冲突,因此在读取数据时不加锁,只有在提交数据时才会进行冲突检查。通常通过版本号或时间戳来检测是否发生了并发修改。

实现方式:
1.在数据表中添加一个版本号(version)或时间戳字段。
2.在读取数据时,客户端读取数据的同时也会读取这个版本号或时间戳。
3.在更新数据时,客户端会将数据提交到数据库,并同时检查提交时的版本号或时间戳是否和读取时一致。如果一致,表示没有其他事务修改该数据,更新成功;否则,更新失败,需要重新读取数据并再次提交。

使用场景:

  • 低并发系统: 在高并发情况下,乐观锁的性能较差,因为冲突的概率增加,但在低并发下非常适合使用。
  • 操作不频繁的资源: 当数据更新的频率不高时,乐观锁能够有效减少数据库的锁竞争。
  • 非关键性的数据: 数据冲突后可以容忍重试的场景,如某些临时数据的处理。
1
2
3
4
5
6
7
8
9
10
#### 乐观锁实现加一操作代码
# 我们可以看到,只有当对数量-1操作时才会加锁,只有当程序中值和数据库中的值相等时才正真执行。
'''
//不加锁
select id,name,stock where id=1;
//业务处理
begin;
update shop set stock=stock-1 where id=1 and stock=stock;
commit;
'''

1.4 行级锁 & 表级锁

  • 行级锁(写多读少)开销大,加锁慢,锁定粒度最小,发生锁冲突概率最低,并发度最高
  • 表级锁(适合大量的读操作)开销小,加锁快,锁定粒度大、发生锁冲突最高,并发度最低

__END__