美文网首页
MySQL数据库锁

MySQL数据库锁

作者: 江月照我眠 | 来源:发表于2022-02-05 19:56 被阅读0次

存在并发操作的时候,必然需要一种机制来保证数据的完整性和一致性。锁就是这样一种技术实现。

当多个用户并发地存取数据时,数据库会存在多个事务同时存取同一数据的情况。如果对此不加控制,就很有可能会读取和存储不正确的数据,破坏数据库的完整性和一致性。当事务在对某个数据进行操作前,先向系统发出请求,对其加锁。加锁后事务就对数据对象有了一定的控制。

在MySQL中存在多种锁,根据概念,可分为悲观锁和乐观锁

  • 悲观锁,也叫悲观并发控制,顾名思义就是对事务操作很悲观,认为每次拿数据都会对数据进行修改,因此每次读取数据都会加锁。当事务A对某行数据应用了锁,只有当事务A把锁释放以后,其他事务才能执行与该锁冲突的操作,这里的事务A施加的锁就叫悲观锁。

  • 乐观锁,也叫乐观并发控制,顾名思义就是对事务操作比较乐观,认为多用户并发的事务在处理时不会互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了数据。如果有,那么当前正在提交的事务会进行回滚。

在MySQL数据库中,悲观锁依靠数据库提供的锁机制来实现的,乐观锁需要使用者自己去实现

从锁的功能上看,可以分为共享锁、排他锁(独占锁)、意向锁等。共享锁也称S锁,排他锁也被称为X锁。

从锁的粒度来看,又可以分为表锁、页锁、行锁。在MySQL中,最常见的是表锁和行锁。其中MyISAM引擎只有表锁,而InnoDB既有表锁也有行锁

表锁的特点是开销小,加锁块,不会出现死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低。相反,行锁的加锁开销比较大,发生冲突的概率比较低,但是并发度也高,带来的副反应就是死锁。而页锁则界于表锁和行锁之间。

1. MySQL表锁

MySQL的表锁有两种模式:表共享锁和表独占锁。

  • 对于MyISAM的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;
  • 对于MyISAM的写操作,则会阻塞其他用户对于同一表的读和写请求;
  • MyISAM的读和写操作之间,以及写和写操作之间是串行的。
如何加表锁

MyISAM表在执行查语句之前,会自动给涉及的所有表加读锁,在执行更新操作(增删改)前,会自动给涉及的表加写锁,不需要用户显式加锁。

给MyISAM表显式加锁,一般是为了一定程度模拟事务操作,实现对某一时间点多个表的一致性读取。比如一个订单表order有total字段,有订单明细表order_detail记录一个订单多个产品的金额小计money。如果你要判断这两个表的金额是否相等,可能需要分别查询两个表的金额:

SELECT SUM(`money`) FROM `order_detail`;
SELECT SUM(`total`) FROM `order`;

如果不加锁,在执行一个表的查询过程中,order_detail表可能已经发生了改变,因此正确的做法是:

LOCK TABLES `order` READ LOCAL,`order_detail` READ LOCAL;
SELECT SUM(`money`) FROM `order_detail`;
SELECT SUM(`total`) FROM `order`;
UNLOCK TABLES;

需要说明的是,在锁表的同时加了local选项,其作用就是在满足MySQL表并发插入条件的情况下,允许其他用户在表尾插入数据。

在用LOCK TABLES给表显示加锁时,必须同时取得所有涉及表的锁,在执行LOCK TABLES后,当前会话只能访问显式加锁的这些表,不能访问未加锁的表;同时,如果加的是读锁,那么只能执行查询操作,而不能操作更新操作,非当前会话可以执行UPDATE操作,但会被阻塞,可以在满足一定条件时执行INSERT操作,不受阻塞。被加锁的会话不会读取到其他会话的INSERT数据,自动加锁的情况下也基本如此,这也是MyISAM表不会出现死锁的原因。

MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制并发插入行为,因此上述提到的“满足一定条件”就是:

  • concurrent_insert设置为0时,不允许并发插入。
  • concurrent_insert设置为1时,如果MyISAM允许线程读表的同时,另一个线程从表尾插入记录。这是默认设置。
  • concurrent_insert设置为2时,无论MyISAM有没有空间,都允许在表尾插入记录。

无论是读锁还是写锁,两者的机制和上面提到的差不多。前面提到,MyISAM表的读锁和写锁是互斥的,读之间是串行的。

那么如果一个进程请求某个MyISAM表既有读锁又有写锁时,MyISAM的默认设置是写优先,可以修改LOW_PRIORITY_UPDATES参数,也可以在INSERT、UPDATE、DELETE语句中指定LOW_PRIORITY选项来调节读写锁的竞争。

另外,MySQL还提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL暂时将写请求的优先级降低,给读进程一定获得锁的机会。

2. MySQL行锁

MySQL行锁主要存在于InnoDB引擎中。InnoDB主要实现了以下两种类型的行锁:

  • 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
  • 排它锁(X):允许获得排他锁的事务更新数据,阻塞其他事务取得相同数据集的共享读锁和排他写锁。

InnoDB还存在一种意向锁,分为意向共享锁和意向排他锁,这两种锁都由系统自动添加和自动释放,无需人工干预。

依然使用上面的两张表,把引擎修改为InnoDB。注意:使用悲观锁时,必须关闭MySQL默认开启的自动提交模式。默认情况下,当你执行更新操作后,MySQL会立刻将结果进行提交。

读共享锁:

SET autocommit = 0;
SELECT `money` FROM `order_detail` WHERE `id` = 2 LOCK IN SHARE MODE;
UPDATE `order_detail` SET `money` = 14 WHERE `id` = 2;
SELECT `money` FROM order_detail WHERE `id` = 2;
COMMIT;

写排他锁(独占锁):

SET autocommit = 0;
SELECT `money` FROM `order_detail` WHERE `id` = 2 FOR UPDATE;
UPDATE `order_detail` SET `money` = 14 WHERE `id` = 2;

如果事务A先获得了某行的写共享锁,事务B可以获得该记录,但不能对数据加写锁(可以加S锁),必须等待事务A提交或回滚后才可以加X锁。

需要注意的是,InnoDB并不总是给数据加行锁,有时候也会加表锁。原因在于,InnoDB行锁是通过给索引上的索引项加锁来实现的。这也意味着只有通过索引条件检索数据,InnoDB才使用行锁,否则将使用表锁。另外要注意,因为MySQL的行锁是针对索引加的锁,所以虽然可能访问的是不同的行记录,但是如果条件中用了相同的索引建,还是会造成锁冲突。比如order_detail表的order_id是加了索引的,下面查询了两条订单明细,会出现锁冲突:

select * from order_detail where order_id = 1 and good_id = 1;
select * from order_detail where order_id = 1 and good_id = 2;

此外,还存在一种“间隙锁”的概念。当我们使用范围查询而非相等条件查询,并请求共享或排他锁时,InnoDB会自动对符合条件的已有数据记录的索引项加锁,对于键值在条件范围内但并不存在的记录,我们称之为“间隙”(GAP),InnoDB也会对这个间隙加锁,这种机制就是所谓的“间隙锁”。

InnoDB表大部分情况下都应该使用行锁,因为事务和行锁往往就是我们选择InnoDB表的理由。但个别情况下,我们也可以考虑使用表锁:

  • InnoDB使用表锁的情况一:事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,效率比较低不说,还可能造成其他事务长时间的等待和锁冲突,建议用表锁提高事务执行速度。
  • InnoDB使用表锁的情况二:事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况可以考虑使用表锁以避免死锁、减少数据库因事务回滚等带来的开销。

InnoDB下使用表锁需要注意以下两点。

    1. 使用LOCK TABLES虽然也可以加表锁,但由于表锁并不是由InnoDB引擎管理,而是由MySQL Server负责的,因此仅当autocommit = 0、innodb_table_locks = 1(默认设置)时,InnoDB层才能知道MySQL加的表锁,MySQL Server也才能感知InnoDB加的行锁。这种情况下,InnoDB才能自动识别涉及表级锁的死锁,否则InnoDB无法自动检测并处理死锁。
    1. 在LOCK TABLES对InnoDB表加锁时要注意将AUTOCOMMIT设为0,否则MySQL不会给表加锁;在事务结束前,不要用UNLOCK TABLES释放表锁,因为UNLOCK TABLES会隐式地提交事务;COMMIT或ROLLBACK并不能释放用LOCK TABLES加的表锁,必须要用UNLOCK TABLES释放表锁。

正确写法举例,写表t1并从表t2读:

SET AUTOCOMMIT = 0;
LOCK TABLES t1 WRITE,t2 READ;
-- todo ... [do sth with t1 and t2 here];
COMMIT;
UNLOCK TABLES;

相关文章

  • MS汇总

    数据库相关[MS-关于锁(乐观锁,悲观锁,行锁、表锁,共享锁,排他锁)Mysql索引优化Mysql查询优化Mysq...

  • MySQL 锁表

    Linux System Environment MySQL锁表简介 MySQL锁表是禁止用户在数据库增加/删除/...

  • Mysql

    MySQL InnoDB中使用悲观锁 要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认...

  • MySQL表级锁和行级锁

    MySQL学习笔记(五):MySQL表级锁和行级锁 一:概述 相对其他数据库而言,MySQL的锁机制比较简单,其最...

  • 5、全局锁和表锁

    根据加锁范围:MySQL里面的锁可以分为:全局锁、表级锁、行级锁 全局锁: 对整个数据库实例加锁。 MySQL提供...

  • MySQL锁简介

    备注:测试数据库版本为MySQL 8.0 这个blog我们来聊聊MySQL 事务 一.MySQL锁概述 数据库锁定...

  • mysql学习day3

    mysql的锁根据加锁范围:MySQL里面的锁可以分为:全局锁、表级锁、行级一、全局锁:对整个数据库实例加锁。My...

  • 又一个mysql面试必问的东西mysql锁

    mysql锁 MySQL里面的锁大致可以分成全局锁、表级锁和行锁三类。 全局锁 全局锁就是对整个数据库实例加锁。M...

  • MYSQL(03)-锁

    MySQL 里面的锁大致可以分成全局锁、表级锁和行锁三类 全局锁 全局锁就是对整个数据库实例加锁,mysql提供了...

  • 在kotlin中使用mysql行级锁

    mysql中的锁 首先需要介绍一下mysql的锁。一般我们使用InnoDB数据库引擎+行级锁,SQL为:SELEC...

网友评论

      本文标题:MySQL数据库锁

      本文链接:https://www.haomeiwen.com/subject/rxhykrtx.html