锁是计算机在执行多线程或线程时用于并发访问同一共享资源时的同步机制,MySQL中的锁是在服务器层或者存储引擎层实现的,保证了数据访问的一致性与有效性。
MySQL锁:
- 按粒度分为:全局锁、表级锁、页级锁、行级锁。
- 按模式分为:乐观锁与悲观锁。
- 按属性分为:共享锁、排它锁。
- 按状态分为:意向共享锁、意向排它锁。
- 按算法分为:间隙锁、临键锁、记录锁。
1. 粒度锁
1.1 全局锁
全局锁是对整个数据库范围生效的锁。
在进行一些需要确保整个数据库一致性操作时可以使用全局锁,例如全库备份,全库导出等。
使用方法:
-- 加全局读锁
FLUSH TABLES WITH READ LOCK-- 释放锁
UNLOCK TABLES;
当对数据库加全局读锁后会阻塞其他会话中对该数据库的写操作。注意目前不存在明确的全局写锁
1.2 表级锁
表级锁是对表加锁,特点是开销小,加锁快,粒度大,冲突高,并发度低。
适用于
表级锁有表读锁和表写锁。
会使用表级锁的sql:
-- 给table_name1 加读锁,给table_name2加写锁
LOCK TABLES table_name1 READ, table_name2 WRITE;-- 释放锁
UNLOCK TABLES;
显示加锁
ALTER TABLE
这个命令用于更改表的结构会隐式添加表级写锁,如添加列、删除列、改变列的类型等。执行这个命令的时候,MySQL需要锁定整个表以防止在更改过程中有新的数据写入。
-- 清空表数据
TRUNCATE TABLE table_name;-- 删除表
DROP TABLE table_name;
这两个命令都会隐式添加表级写锁。DROP TABLE命令会删除整个表,而TRUNCATE TABLE命令会删除表中的所有数据.在执行这些命令的时候,MySQL需要锁定整个表以防止在删除过程中有新的数据写入。会在事务结束时自动释放。
1.3 行级锁
行级锁可以对数据库表中的单独一行进行锁定。相比于表级锁,行级锁的粒度更小,因此在处理高并发事务时,能提供更好的并发性能和更少的锁冲突。然而,行级锁也需要更多的内存和CPU资源,因为需要对每一行都进行管理.
行级锁分为共享锁(S锁,读锁)和排他锁(X锁,写锁)
注意:共享锁和共享锁能共存,排他锁和共享锁和排他锁不能共存
会发生行锁的sql:
SELECT ... FOR UPDATE
查询语句后加 FOR UPDATE 会对查询到的行加一个排他锁
SELECT ... LOCK IN SHARE MODE
查询语句后加 LOCK IN SHARE MODE 会对查询到的行加一个共享锁
INSERT ...
UPDATE ...
DELETE ...
这三类语句也对操作的行加排他锁
行级锁通常在事务内使用,事务提交后会自动释放事务内加的所有行级锁,注意不会释放全局锁和表级锁,如果在事务外使用了上述语句,MySQL其实会开启一个隐式的事务,直到语句结束,也就是语句执行完了就会立刻释放锁。
2. 乐观锁,悲观锁
乐观锁(Optimistic Locking)是一种在数据库操作中用于处理并发问题的技术。它的基本思想是假设在多个事务同时访问同一条数据时,冲突发生的概率较低,因此在操作数据时不会立即进行锁定,而是在提交数据更改时检查是否有其他事务修改了这条数据。如果没有,就提交更改,否则就回滚事务。
在MySQL中,乐观锁并没有内置的实现,但是可以通过一些编程技巧来实现。一种常见的实现方式是使用版本号(或时间戳)字段。每当一条记录被修改时就增加版本号(或更新时间戳)。在更新记录时,先检查版本号(或时间戳)是否和读取记录时的版本号(或时间戳)一致,如果一致则执行更新并增加版本号(或更新时间戳),否则就拒绝更新。
UPDATE your_table SET name = 'New Name', version = version + 1
WHERE id = 1 AND version = expected_version;
这样就可以保证只有当记录没有被其他事务修改时,当前事务的更改才会被提交乐观锁的优点在于,由于大部分时间都不需要锁定,所以在冲突较少的情况下可以获得较高的并发性能。然而,如果冲突较多,那么乐观锁可能会导致大量的事务回滚,从而影响性能。因此,选择使用乐观锁还是其他锁定技术,需要根据实际的并发情况和性能需求来决定。
优点:
- 不会阻塞其他事务的读取操作,提高了数据库的并发性能。
- 适用于读操作频繁、写操作相对较少的场景。
缺点:
- 如果并发冲突频繁,会导致大量的回滚和重试操作,降低系统性能。
- 需要应用程序逻辑来确保乐观锁的正确实现,增加了开发和维护的复杂性。
悲观锁:我们上面介绍的行级锁就是一种悲观锁
3. 意向锁
意向锁分为意向共享锁和意向排他锁 ,意向锁是一个表级锁
当某个事务添加行锁时会自动添加一个意向锁,类型取决于行锁的类型。
当表被加了意向锁,其他事务想要对表加锁就不用一行一行遍历查看是否存在行锁,只需查一次意向锁即可,提高了效率
4. 间隙锁,记录锁,临键锁
间隙锁,临键锁,记录锁都是行锁
4.1 间隙锁
间隙锁锁定的是行与行之间的间隙,间隙锁的存在,主要是为了解决幻读问题。所谓幻读,是指在一个事务内读取某个范围的记录时,另外一个事务在该范围内插入了新的记录,当第一个事务再次
读取该范围的记录时,会发现有些原本不存在的记录,这就是幻读。
间隙锁只在可重复读及序列化级别下使用,并且查询条件使用的索引字段才可能加间隙锁
1. 范围查询:
SELECT * FROM your_table WHERE id BETWEEN 10 AND 20 FOR UPDATE;
这条语句会在 id 列的值为 10 到 20 之间的每一行数据上设置间隙锁。具体来说,MySQL会在 id 值为 10 和 20 之间的数据行的间隙上设置锁,以防止其他事务在这个范围内插入新的数据行。
2. 唯一索引扫描:
SELECT * FROM your_table WHERE unique_column = 100 FOR UPDATE;
如果 unique_column 是一个唯一索引列,MySQL会在 unique_column 等于 100 的数据行上加锁。同时,如果 unique_column 列上不存在值为 100 的行,MySQL 也会在唯一索引中对值为 100 的位置加锁,以防止其他事务在这个位置插入新数据。
3. 非唯一索引范围查询:
SELECT * FROM your_table WHERE non_unique_index_column BETWEEN 50 AND 100 FOR UPDATE;
这条语句会在 non_unique_index_column 列的值在 50 和 100 之间的每一行数据上设置间隙锁。MySQL会在 non_unique_index_column 值为 50 和 100 之间的数据行的间隙上设置锁,以防止其他事务在这个范围内插入新的数据行。
4.2 记录锁
记录锁就是我们上面讲行级锁对单行记录锁定,防止其他事务更新的锁:
4.3 临键锁
临键锁可以理解为间隙锁加记录锁,其他事务既不能更改锁定的数据,也不能插入
每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的一点是,IoDB中行级锁是基于索实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。
假设现在某表上存在 非唯一索引列数据[12, 24, 32, 68]
那么存在临键锁(-∞, 12], (12, 24], (24, 32], (32, 68], (68, +∞]
如果使用 SELECT ... FOR UPDATE 语句查询时,事务会获取查询到数据相邻的临键锁。
例如查询了 24 会获取(12, 24], (24, 32]的临键锁,此时其他事务不能修改该范围的值,也不能插入[12, 33]区间的值