针对 SELECT FOR UPDATE 语句分别作了测试验证:
Result 行数 | LOCK 数量 | RowID锁 | 唯一键锁 | SQL | |||
---|---|---|---|---|---|---|---|
主键搜索 | 1 | 1 | 1 | 0 | SELECT * FROM T WHREE ID=1 | ||
唯一索引搜索 | 1 | 2 | 1 | 1 | SELECT * FROM T WHREE UNIQUE_INDEX=1 | ||
普通索引搜索 | 1 | 1 | 1 | 0 | SELECT * FROM T WHREE INDEX=1 | ||
无索引搜索 | 1 | 1 | 1 | 0 | SELECT * FROM T WHREE COLUMN=1 | ||
未命中数据 | 0 | 0 | 0 | 0 | SELECT * FROM T WHREE C1=0 AND C1=1 | ||
命中多条数据 | 2 | 2 | 2 | 0 | SELECT * FROM T | ||
命中多条数据+唯一键搜索 | 2 | 4 | 2 | 2 | SELECT * FROM T WHREE UNIQUE_INDEX IN (1,2) |
我们发现一个比较奇怪的现象,如果 SQL
语句的 WHERE
语句命中了唯一索引,那么 TIDB
将会对唯一索引也加锁。其他情况下,不会对唯一索引进行加锁。
那么 SELECT FOR UPDATE 语句是否有必要对唯一索引加锁呢?
个人认为好像并不需要,因为 RowID 已经被加锁,所以被锁定的行数据不会被更新。
同时其他行数据是否可以更新唯一键呢?也不会。例如:
CREATE TABLE `MANAGERS_UNIQUE` (
`MANAGER_ID` int(11) NOT NULL, `FIRST_NAME` varchar(45) NOT NULL,`LAST_NAME` varchar(45) NOT NULL,`LEVEL` int(11) DEFAULT NULL,
PRIMARY KEY (`MANAGER_ID`) /*T![clustered_index] CLUSTERED */,
UNIQUE KEY `FIRST` (`FIRST_NAME`),
KEY `level` (`LEVEL`)
)
+------------+------------+-----------+-------+
| MANAGER_ID | FIRST_NAME | LAST_NAME | LEVEL |
+------------+------------+-----------+-------+
| 14273 | Brad7 | Craven7 | 7 |
| 14274 | Brad8 | Craven8 | 8 |
+------------+------------+-----------+-------+
事务一:
BEGIN PESSIMISTIC;
mysql> select * from MANAGERS_UNIQUE where MANAGER_ID=14273;
+------------+------------+-----------+-------+
| MANAGER_ID | FIRST_NAME | LAST_NAME | LEVEL |
+------------+------------+-----------+-------+
| 14273 | Brad7 | Craven7 | 7 |
+------------+------------+-----------+-------+
事务二:
BEGIN PESSIMISTIC;
update MANAGERS_UNIQUE SET FIRST_NAME ="Brad7" WHERE MANAGER_ID = 14274;
这种情况下,事务二会首先检查唯一索引 FIRST_NAME
的唯一性,发现已经有一个 “Brad7
” 的唯一索引后,会直接返回错误。因此好像并不需要对唯一键进行加锁就可以实现这个功能。(实际上 TIKV
的 acquire_pessimistic_lock
接口的 should_not_exist
就是这个作用)
那么这个唯一索引是否有其他原因或者场景,必须加上锁才可以呢?
除了 Select for update 语句,Delete 语句也会对唯一索引加悲观锁,Update 也会对原有旧的唯一索引加悲观锁,详见:专栏 - TIKV 分布式事务--加锁的 KEY 是什么 | TiDB 社区
这些加锁操作内部核心的原因是什么呢?如果悲观锁的场景下,仅仅对 rowID 加锁,不对唯一索引加锁的话,是否可以减少一些系统的压力。