1.概念
锁是计算机协调多个进程或者线程并发访问某一资源的机制。那么如何保证数据并发访问的一致性、有效性是数据库必须解决的一个问题,锁的冲突也是影响数据库并发访问性能的一个重要因素,所以数据库中锁的应用极为重要,其复杂度也更高。
锁的分类,以锁的颗粒度为三类:
- 全局锁:锁定数据库中的所有表。
- 表级锁:每次操作锁住整张表。
- 行级锁:每次操作都锁到对应行的数据。
接下来就分别对这几种锁做一个解释。
数据库锁是数据库管理系统用来管理对数据库中数据的并发访问的一种机制。锁的存在主要是为了保证数据的完整性和一致性,以及支持事务的原子性。以下是全局锁、表级锁和行级锁的详细解释:
1. 全局锁(Global Lock):
• 全局锁是一种锁定数据库中所有表的锁。当数据库处于只读模式时,通常使用全局锁。
• 在这种模式下,没有写操作被允许,只能执行读操作。这可以用于数据库的备份或维护期间,以防止数据被修改。
• 全局锁的范围是最广的,它锁定了整个数据库实例,所有试图进行写操作的事务都会被阻塞。
sql
-- 全局锁,整个数据库处于只读状态,其他操作均阻塞
FLUSH TABLES WITH READ LOCK
-- 释放全局锁
UNLOCK TABLES
2. 表级锁(Table-Level Lock):
• 表级锁是锁定数据库中特定表的锁。当一个事务对表进行写操作(如 INSERT、UPDATE、DELETE)时,通常会请求表级锁。
• 表级锁会阻止其他事务对同一张表进行写操作,但仍然允许其他事务对这张表进行读操作,这取决于锁的类型(共享锁或排他锁)。
• 表级锁的粒度比全局锁小,它只锁定涉及的特定表,其他表可以被并发访问。
3. 行级锁(Row-Level Lock):
• 行级锁是锁定数据库表中特定行记录的锁。这种锁的粒度最细,只锁定事务操作影响到的具体行。
• 行级锁通常在执行精确的数据操作时使用,如 UPDATE 某个具体的行或 DELETE 某个具体的行。
共享锁和排他锁
- 共享锁(S Lock):其他事务可读,但不可写
- 排他锁(X Lock):其他事务不能读取也不能写
这两种行锁之间的兼容性如下:
共享锁和共享锁可以兼容,排他锁和其它锁都不兼容。例如,事务 A 获取了一行数据的共享锁,事务 B 可以立即获得该数据行的共享锁,也就是锁兼容;但是此时事务 B 如果想获得该数据行的排他锁,则必须等待事务 A 释数据行上的共享锁,此种情况存在锁冲突。
默认情况下,数据库中的锁都可以自动获取;但是也可以手动为数据进行加锁。我们来看一个示例,首先创建一个表:
sql
create table t(
id int auto_increment primary key,
c1 int,
c2 int,
c3 int
);
create unique index idx_t_c1 on t(c1);
create index idx_t_c2 on t(c2);
insert into t(c1,c2,c3) values (1,1,1),(2,3,4),(3,6,9);
其中,id 是主键;c1 上创建了一个唯一索引;c2 上创建了一个非唯一索引;c3 上没有索引。
接下来的示例都使用 MySQL 默认的隔离级别 Repeatable Read,除非另有说明。
然后创建两个数据库连接 T1 和 T2,先在 T1 中锁定一行数据:
sql
-- T1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t where id = 1 for share;
+----+------+------+------+
| id | c1 | c2 | c3 |
+----+------+------+------+
| 1 | 1 | 1 | 1 |
+----+------+------+------+
1 row in set (0.00 sec)
我们在事务中使用select ... for share
语句获得了数据行 id = 1 上的共享锁;对于 MySQL 8.0 之前的版本,可以使用select ... lock in share mode
命令。
由于 InnoDB 中的自动提交 autocommit 默认设置为 ON,我们必须在事务中为数据行加锁;或者将 autocommit 设置为 OFF。
然后在 T2 中执行以下语句:
sql
-- T2
mysql> select * from t where id = 1 for share;
+----+------+------+------+
| id | c1 | c2 | c3 |
+----+------+------+------+
| 1 | 1 | 1 | 1 |
+----+------+------+------+
1 row in set (0.00 sec)
结果显示,在 T2 中成功获取改行数据上的共享锁。然后尝试获取排他锁:
sql
-- T2
mysql> select * from t where id = 1 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
使用select ... for update
命令获取排他锁,此时该命令会一直处于等待状态并且最终超时。也就是说,共享锁和排他锁不兼容。
最后,在 T1 中提交或者回滚事务:
sql
-- T1
mysql> commit;