MySQL-事务和锁机制

ragnar 10月前 ⋅ 203 阅读

MySQL的事务的ACID特性,事务并发问题,以及事务的隔离级别。关于锁机制方面,简单聊一聊锁思想,常见的锁,以及如何加锁。

1 事务

1.1 事务的ACID特性

  • A 原子性:Atomic,事务内的所有操作,要么全部成功,要么全部失败。
  • C 一致性:Consistent,事务前后,涉及的数据是从一致的状态到另一个一致的状态,也就不能出现脏写。
  • I 隔离性:Isolation,事务与事务之间相互隔离,互不干扰。
  • D 持久性:D,事务提交成功,数据便持久化保持到磁盘。

1.2 事务并发的问题

  • 脏写:多个事务并发对同一数据进行操作,且都操作成功了。结果是最后一个事务的操作覆盖了前面的。
  • 脏读:一个事务读取到了另一个事务未提交的数据。
  • 不可重复读:一个事务多次对同一数据进行读取,读到的数据会变化。
  • 幻读:一个事务读取到了另一个事务已提交的新增数据。

1.3 事务的隔离级别

查看当前数据库的隔离级别

show variables like 'tx_isolation';
  • 读未提交:存在脏读不可重复读幻读等并发问题。
    set tx_isolation='read-uncommitted';
    
  • 读已提交:存在不可重复读幻读等并发问题。
    set tx_isolation='read-committed';
    
  • 可重复读:存在幻读的并发问题。
    set tx_isolation='repeatable-read';
    
  • 可串行化:没有并发问题,因为读写操作都会加锁,并发度最差。
    set tx_isolation='serializable';
    

由于MySQL的四个隔离级别的写操作都会隐式加锁,确保了同一时刻只有一个事务在对涉及的数据进行写操作,所以没有脏写的问题。

2 锁机制

2.1 乐观锁 & 悲观锁

乐观锁与悲观锁就是两种思想,不是真正的锁。

乐观锁是认为业务中读操作远多于写操作,并发写操作的可能性低,操作时不加锁。
悲观锁则与乐观锁相反,操作时直接加锁。

2.2 读锁 & 写锁

读锁: 也叫共享锁(shared lock)。 读锁是共享的,是相互不阻塞的。多个事务在同一时刻可以同时读取同一个资源,互不干扰。但会阻塞写操作。

写锁: 也叫排它锁(exclusive lock)。 写锁是排他的,加锁后会阻塞其它事务获取该资源的写锁和读锁。

2.3 锁的粒度

2.3.1 表锁

锁粒度最大,锁的是整个表。InnoDBMyISAM等存储引擎都支持。

优点:开销小,上锁快。

缺点:并发度低。

锁操作

  • 显式锁操作 be like:
    # 加读锁
    lock table TableName read
    
    # 加写锁
    lock table TableName write
    
    # 查看被上锁的
    show open tables;
    
    # 释放锁
    unlock tables;
    
  • 隐式锁操作:一般是DDL语句,比如:给表加字段、加索引等等。在执行前会给整个表加锁,执行完毕会自动解锁。

阻塞问题

  • 如果给表上的是写锁,其它事务的读操作写操作、获取读锁写锁都会被阻塞。
  • 如果给表上的是读锁,其它事务可以获取读锁和进行读操作,但写操作和获取写锁会被阻塞。

2.3.2 行锁

锁粒度最小,锁的是对应的行记录。MyISAM不支持,InnoDB支持。

优点:锁粒度小,并发度高。

缺点:开销大,上锁慢。

锁操作

  • 显式锁操作 be like:

    # 加写锁(for update)
    select * from account where id = 1 for update;
    
    # 加读锁(lock in share mode)
    select * from account where id = 1 lock in share mode;
    
    # 解锁:事务提交/回滚
    
  • 隐式锁操作: 一般是updatedelete语句等写操作,InnoDB存储引擎都会自动对要操作的行记录加锁。

阻塞问题

  • 如果给表记录上的是写锁,其它事务可以进行读操作,但写操作、获取读锁写锁会被阻塞。
  • 如果给表记录上的是读锁,其它事务可以获取读锁和进行读操作,但写操作和获取写锁会被阻塞。

2.3.3 间隙锁

间隙锁(Gap Lock),锁的不是记录,而是已经存在的记录之间的间隙。只在可重复读的隔离级别下生效

优点

  • 可以用来防止幻读问题;
  • 锁粒度适中。

缺点:在可重复读隔离级别下才会生效。

为什么说锁的是间隙呢?

先确认当前事务隔离级别是REPEATABLE-READ可重复读。

show variables like 'tx_isolation';

现有student表如下(主键ID列中3-10之间有空隙):

事务A,去执行加间隙锁操作:

# 开启事务
begin;

# 对id列的3-10之间加`间隙锁`
select * from student where id > 3 and id < 10 for update;

此时,事务B去插入一条id为8的记录,会发现被阻塞了,直到事务A提交/回滚事务:

insert into student (id, name, age, address) values (8, 'Li', 20, '香港');

如果,把间隙锁的范围放大会怎么样? 比如:之前锁的是(3, 10)这个区间,现在我们把10也包含进去===>(3, 10]。

你会发现:id大于10的(比如id=12)也会被阻塞了,也就说10到正无穷都被锁了。

2.3.4 临键锁

临键锁(Next-key Locks),是行锁间隙锁的组合。


全部评论: 0

    我有话说:

    目录