0%

MySQL 数据库锁机制

数据库是一个多用户共享的资源,这样的话对于多个用户在存取同一数据的时候,就会出现问题,举个最经典的问题—-票务系统,如何保证数据的正确性。当只剩下最后1张票的时候,两个用户同时取到数据并更新,那么最后是谁买到票了呢?

数据库事务

数据库事务是指单个逻辑工作单元执行一系列操作,要么完成执行,要么完成不执行。数据库事务必须满足ACID(原子性、一致性、隔离性、持久性)。

  • 原子性:对于其数据的修改,要么全部执行,要么完全不执行。原子性消除了系统处理操作子集的可能。
  • 一致性:事务完成时,必须是所有的数据都保持一致
  • 隔离性:由并发事务所做的修改必须与任何其他事务并发事务所做的修改隔离
  • 持久性:完成事务后,对系统的修改时永久性的

事务隔离级别

  • 未提交读:当前事务未提交、其他事务也能读到
  • 提交读:当前事务提交之后,其他事务才能看到
  • 可重复读:该级别解决了同一事务多次读取同样记录的结果是一致的。但是理论上,还是无法解决另一个幻读问题。幻读 是指当前事务读取某个范围内的记录时,其他事务在该范围又加入新的纪录,之前的事务再次从该范围读取数据时,会产生幻行。这个的换行是对于 insert 操作来说的,对于 update 操作能保证没有幻行问题。
  • 可串行化:最高的事务隔离级别。强制事务串行化执行,避免了幻读问题,这种级别会在数据的每一行都加锁,会产生大量的超时和锁竞争,实际中很少用到,除非要确保数据的一致性且没有并发问题

怎么理解脏读、不可重复读和幻读

脏读

脏读是读到了未提交事务的数据。只有在读未提交隔离级别下,才会出现脏读。

解决脏读的办法就是升级事务隔离级别,比如读已提交。

不可重复读

事务A 先读取一条数据,然后执行逻辑的过程中,事务B更新了这条数据,事务A在读取时,发现数据不一致了,这种现象就是不可重复读。

  • 简单理解就是两次读取的数据中间被修改,对应的隔离级别是读未提交或读已提交
  • 不可重复读的解决方法就是升级事务隔离级别,比如可重复读

幻读

在一个事物内,同一条查询语句在不同时间段内,得到不同的结果集。幻读和不可重复读的区别,简单理解,不可重复读发送在update,幻读发生在insert或delete。

  • 想解决幻读不能升级事务隔离级别到可串行化,这种级别下数据库也是去了并发处理能力
  • 行锁解决不了幻读,因为即使锁住所有记录,还是阻止不了插入新数据
  • 解决幻读的办法是锁住记录之间的间隙,MySQL InnoDB 引入了间隙锁(Gap Lock)。

今天我们所讲的数据库是MySQL。InnoDB支持行/表级锁,默认行级锁。

共享锁

又称读锁(S锁),是读取操作创建的锁,其他用户可以并发的读取数据,但任何事务都不能对数据修改,直至释放所有的共享锁。

用法:

1
SELECT ... lock in share mode;

排他锁

又称写锁(X锁),如果事务A对某数据加上了排他锁后,别的事物不能对该数据加任何类型的锁

用法:

1
SELECT ... FOR UPDATE;

共享锁和排他锁

1、在同一资源上面,不能共同存在共享和排他
2、可以共同存在共享锁
3、不能共同存在排他锁

行级锁和表锁

行级锁:一种X锁,防止其他事务修改加锁的这行数据
如果被锁定的字段不是主键,也没有针对它建立索引,数据库会锁住扫过的所有数据。
表级锁:对当前整张表进行加锁

乐观锁

乐观锁是基于一种具有“乐观”的思想,假设数据库操作的并发非常少,多数情况下是没有并发的,更新是按照顺序执行的,少有的一些并发通过版本控制来防止脏数据的产生。事务每次操作的时候,别的事务都不会修改这些数据。所以在访问之前不要求上锁,只是在更新修改操邹的时候判断一下在访问期间有没有别的事务对数据修改,判断是否冲突。这种适合多读的应用类型。
通常可以在数据表中加一个version字段,每次操作,version加1。更新是判断version的值是否和之前相等;或者判断时间戳来控制版本。
乐观锁在同一时刻,只有一个请求会成功,适用并发不高的场景。

悲观锁

事务提交操作的时候,别的事务也对该数据块进行了修改,所以访问之前都需要上锁。这个需要数据库自己实现,我们直接调用数据库相关语句就可以了。

1
select ... where id = $id for update

悲观锁是在数据库引擎层次实现的,它能够阻止所有的数据库操作。但是为了更新一条数据,需要提前对这条数据上锁,直到这条数据处理完成,事务提交,别的请求才能更新数据,因此,悲观锁的性能比较低下,但是由于它能够保证更新数据的强一致性,是最安全的处理数据库的方式,因此,有些账户、资金处理系统仍然使用这种方式,牺牲了性能,但是获得了安全,规避了资金风险。

间隙锁(Gap Lock)

间隙锁是InnoDB 在可重复度隔离级别下解决幻读问题引入的锁机制。在可重复度隔离级别下,数据库通过行锁和间隙锁共同组成的(next-key lock)来实现。

产生间隙锁条件(PR 隔离级别下):

  • 使用普通索引锁定
  • 使用多列唯一索引
  • 使用唯一索引锁定多行记录

如果想打开MySQL间隙锁,使用命令 show variables like 'innodb_locks_unsafe_for_binlog'; 查询 innodb_locks_unsafe_for_binlog

Reference

客官,赏一杯coffee嘛~~~~