黄东旭解析 TiDB 的核心优势
526
2024-03-08
一般为了表述的简洁,我们在介绍 TiDB
分布式事务的时候,经常说 TIDB
会对 KEY
加锁,或者对 KEY
进行锁的检测,从而保障事务的逻辑性和隔离性。
但是这个 KEY
到底是什么,对想要了解分布式事务的同学来说,可能不够直观。
我们今天将会使用具体的 SQL,结合不同类型的主键与索引,来观察 TIKV
分布式事务中的锁到底是什么。
开始之前,需要参考文章:《说计算》来了解关系型数据库和 KV
键值之间的关联
TiDB 表中的一条数据,最后会对应多个 Key-Value 去存储。我们来一起回顾一下重点:这些 Key-Value 分为两类:
RowID => Value : 主键信息,该类数据存储着当前对应行的所有信息。
{ Key:
tablePrefix{tableID}_recordPrefixSep{rowID
}, Value:[col1, col2, col3, col4]
}Index Key => RowID: 索引信息,该类数据为索引到 RowID 的映射。表有多少的索引,每行数据就有多少个这样的 Key-Value。根据是否是唯一索引,分为如下两种类型
{ Key:
tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue
, Value:rowID
}{ Key:
tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue_rowID
, Value:null
}
理解了上面,其实分布式事务就是对这些 KEY
进行加锁: 一个是对 主键 进行加锁,一个是对 唯一索引 进行加锁。
对于普通的索引来说: (tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue_rowID
)
由于分布式事务会对 rowID
主键进行加锁,因此普通索引的 KEY
实际上受到了 rowID
这个主键上面锁的保护,不需要加锁。
测试过程中,建表语句如下:
CREATE TABLE `MANAGERS_UNIQUE` ( `MANAGER_ID` int(11) NOT NULL, `FIRST_NAME` varchar(45) NOT NULL, `LAST_NAME` varchar(45) NOT NULL, `LEVER` int(11) DEFAULT NULL, PRIMARY KEY (`MANAGER_ID`) UNIQUE KEY `FIRST` (`FIRST_NAME`), KEY `LEVEL` (`LEVEL`) )
插入数据:
mysql> INSERT INTO MANAGERS_UNIQUE(MANAGER_ID,FIRST_NAME, LAST_NAME) VALUES (14273,'Brad7','Craven7',7); mysql> INSERT INTO MANAGERS_UNIQUE(MANAGER_ID,FIRST_NAME, LAST_NAME) VALUES (14274,'Brad8','Craven8',8);
执行的 SQL
为:
mysql> BEGIN PESSIMISTIC; Query OK, 0 rows affected (0.00 sec) mysql> INSERT INTO MANAGERS_UNIQUE(MANAGER_ID,FIRST_NAME, LAST_NAME, LEVEL) VALUES ('14275','Brad9','Craven9',9); Query OK, 1 row affected (0.01 sec)
此时扫描 TIKV
所有的 LOCK
记录:
./tikv-ctl --data-dir tikv-0/data raw-scan --cf lock key: "zt\200\000\000\000\000\000\000\377j_i\200\000\000\000\000\377\000\000\002\001Brad\3779\000\000\000\374\000\000\000\374", value: "S:t\200\000\000\000\000\000\000j_i\200\000\000\000\000\000\000\002\001Brad9\000\000\000\374\201\200\240\337\201\206\353\233\006\357\246\002f\0067\2540\033\350\000\001c\0067\2540\033\350\000\002l\000\000\000\000\000\000\000\000\001" key: "zt\200\000\000\000\000\000\000\377j_r\0011427\3775\000\000\000\374\000\000\000\374", value: "S:t\200\000\000\000\000\000\000j_i\200\000\000\000\000\000\000\002\001Brad9\000\000\000\374\201\200\240\337\201\206\353\233\006\365\246\002f\0067\2540\033\350\000\001c\0067\2540\033\350\000\002l\000\000\000\000\000\000\000\000\001" Total scanned keys: 2
分别对两个 KEY 进行解析:
zt\200\000\000\000\000\000\000\377j_i\200\000\000\000\000\377\000\000\002\001Brad\3779\000\000\000\374\000\000\000\374 ├─## decode rocksdb data key │ └─"t\\200\\000\\000\\000\\000\\000\\000\\377j_i\\200\\000\\000\\000\\000\\377\\000\\000\\002\\001Brad\\3779\\000\\000\\000\\374\\000\\000\\000\\374" │ ├─## table prefix │ │ └─table: -2579946652266647504 │ └─## decode go literal key │ └─"t\200\000\000\000\000\000\000\377j_i\200\000\000\000\000\377\000\000\002\001Brad\3779\000\000\000\374\000\000\000\374" │ ├─## decode mvcc key │ │ └─"t\200\000\000\000\000\000\000j_i\200\000\000\000\000\000\000\002\001Brad9\000\000\000\374" │ │ ├─## table prefix │ │ │ └─table: 106 │ │ └─## table index key │ │ ├─table: 106 │ │ ├─index: 2 │ │ └─"\001Brad9\000\000\000\374" │ │ └─## decode index values │ │ └─kind: Bytes, value: Brad9 │ └─## table prefix │ └─table: 255 "zt\200\000\000\000\000\000\000\377j_r\0011427\3775\000\000\000\374\000\000\000\374" ├─## decode rocksdb data key │ └─"t\\200\\000\\000\\000\\000\\000\\000\\377j_r\\0011427\\3775\\000\\000\\000\\374\\000\\000\\000\\374" │ ├─## table prefix │ │ └─table: -2579946652266647504 │ └─## decode go literal key │ └─"t\200\000\000\000\000\000\000\377j_r\0011427\3775\000\000\000\374\000\000\000\374" │ ├─## decode mvcc key │ │ └─"t\200\000\000\000\000\000\000j_r\00114275\000\000\000\374" │ │ ├─## table prefix │ │ │ └─table: 106 │ │ └─## table row key │ │ ├─table: 106 │ │ └─"\00114275\000\000\000\374" │ │ └─## decode index values │ │ └─kind: Bytes, value: 14275 │ └─## table prefix │ └─table: 255
INSERT
比较简单,分别对 RowID
和 唯一索引 进行加锁。
对唯一索引加锁是必要的,防止有其他数据并发的也在更新其唯一索引到相同的值。
mysql> BEGIN PESSIMISTIC; Query OK, 0 rows affected (0.00 sec) mysql> DELETE FROM MANAGERS_UNIQUE WHERE MANAGER_ID=14274; Query OK, 1 row affected (0.02 sec)
./tikv-ctl --data-dir tikv-0/data raw-scan --cf lock key: "zt\200\000\000\000\000\000\000\377j_i\200\000\000\000\000\377\000\000\002\001Brad\3778\000\000\000\374\000\000\000\374", value: "S:t\200\000\000\000\000\000\000j_i\200\000\000\000\000\000\000\002\001Brad8\000\000\000\374\201\200\200\363\203\210\353\233\006\227\230\004f\0067\254@>`\000\001c\0067\254@>`\000\002l\0067\253\223\264<\000\002\001"key: "zt\200\000\000\000\000\000\000\377j_r\0011427\3774\000\000\000\374\000\000\000\374", value: "S:t\200\000\000\000\000\000\000j_i\200\000\000\000\000\000\000\002\001Brad8\000\000\000\374\201\200\200\363\203\210\353\233\006\233\230\004f\0067\254@>`\000\001c\0067\254@>`\000\002l\0067\253\223\264<\000\002\001" Total scanned keys: 2
DELETE
也比较简单,分别对 RowID
和 唯一索引 进行加锁。
DELETE
对唯一索引加锁好像是非必要的?目前本人想不到需要对 DELETE 的唯一索引加锁的场景。
SELECT FOR UPDATE
首先开启悲观事务,同时对主键进行 SELECT FOR UPDATE:
mysql> BEGIN PESSIMISTIC; Query OK, 0 rows affected (0.00 sec) mysql> select * from MANAGERS_UNIQUE where MANAGER_ID=14273 FOR UPDATE; +------------+------------+-----------+ | MANAGER_ID | FIRST_NAME | LAST_NAME | +------------+------------+-----------+ | 14273 | Brad7 | Craven7 | +------------+------------+-----------+ 1 row in set (0.01 sec)
此时扫描 TIKV 所有的 LOCK 记录:
➜ ./tikv-ctl --data-dir tikv-0/data raw-scan --cf lock key: "zt\200\000\000\000\000\000\000\377j_r\0011427\3773\000\000\000\374\000\000\000\374", value: "S*t\200\000\000\000\000\000\000j_r\00114273\000\000\000\374\201\200\260\246\322\243\352\233\006\262\307\025f\0067\251\035$\314\000\001c\0067\251\035$\314\000\002l\0067\244\235?\264\000\002\001" Total scanned keys: 1
对 KEY 进行验证解析:
"74800000000000006A5F72013134323733000000FC" └─## decode hex key └─"t\200\000\000\000\000\000\000j_r\00114273\000\000\000\374" ├─## table prefix │ └─table: 106 └─## table row key ├─table: 106 └─"\00114273\000\000\000\374" └─## decode index values └─kind: Bytes, value: 14273
可以得出结论,名字主键的场景下 SELECT FOR UPDATE 语句只会对 ROWID 进行加锁。
首先开启悲观事务,同时对唯一键进行 SELECT FOR UPDATE:
mysql> BEGIN PESSIMISTIC; Query OK, 0 rows affected (0.00 sec) mysql> select * from MANAGERS_UNIQUE where FIRST_NAME='Brad7' FOR UPDATE; +------------+------------+-----------+ | MANAGER_ID | FIRST_NAME | LAST_NAME | +------------+------------+-----------+ | 14273 | Brad7 | Craven7 | +------------+------------+-----------+ 1 row in set (0.00 sec)
此时扫描 TIKV 所有的 LOCK 记录:
./tikv-ctl --data-dir tikv-0/data raw-scan --cf lock key: "zt\200\000\000\000\000\000\000\377j_i\200\000\000\000\000\377\000\000\002\001Brad\3777\000\000\000\374\000\000\000\374", value: "S:t\200\000\000\000\000\000\000j_i\200\000\000\000\000\000\000\002\001Brad7\000\000\000\374\203\200\260\311\312\251\352\233\006\227\323\003f\0067\251L\251,\000\003c\0067\251L\251,\000\004l\0067\244\235?\264\000\002\001" key: "zt\200\000\000\000\000\000\000\377j_r\0011427\3773\000\000\000\374\000\000\000\374", value: "S:t\200\000\000\000\000\000\000j_i\200\000\000\000\000\000\000\002\001Brad7\000\000\000\374\203\200\260\311\312\251\352\233\006\343\204\003f\0067\251L\251,\000\003c\0067\251L\251,\000\004l\0067\244\235?\264\000\002\001" Total scanned keys: 2
发现有两个 KEY,我们分别解析一下:
"t\\200\\000\\000\\000\\000\\000\\000\\377j_i\\200\\000\\000\\000\\000\\377\\000\\000\\002\\001Brad\\3777\\000\\000\\000\\374\\000\\000\\000\\374" ├─## table prefix │ └─table: -2579946652266647504 └─## decode go literal key └─"t\200\000\000\000\000\000\000\377j_i\200\000\000\000\000\377\000\000\002\001Brad\3777\000\000\000\374\000\000\000\374" ├─## decode mvcc key │ └─"t\200\000\000\000\000\000\000j_i\200\000\000\000\000\000\000\002\001Brad7\000\000\000\374" │ ├─## table prefix │ │ └─table: 106 │ └─## table index key │ ├─table: 106 │ ├─index: 2 │ └─"\001Brad7\000\000\000\374" │ └─## decode index values │ └─kind: Bytes, value: Brad7 └─## table prefix └─table: 255 "zt\\200\\000\\000\\000\\000\\000\\000\\377j_r\\0011427\\3773\\000\\000\\000\\374\\000\\000\\000\\374" ├─## decode rocksdb data key │ └─"t\\200\\000\\000\\000\\000\\000\\000\\377j_r\\0011427\\3773\\000\\000\\000\\374\\000\\000\\000\\374" │ ├─## table prefix │ │ └─table: -2579946652266647504 │ └─## decode go literal key │ └─"t\200\000\000\000\000\000\000\377j_r\0011427\3773\000\000\000\374\000\000\000\374" │ ├─## decode mvcc key │ │ └─"t\200\000\000\000\000\000\000j_r\00114273\000\000\000\374" │ │ ├─## table prefix │ │ │ └─table: 106 │ │ └─## table row key │ │ ├─table: 106 │ │ └─"\00114273\000\000\000\374" │ │ └─## decode index values │ │ └─kind: Bytes, value: 14273 │ └─## table prefix │ └─table: 255
可以得出结论,以唯一索引为搜索条件的话,那么会加两个 KEY 的锁。
一个是 RowID,一个是 UniqueIndex
执行的 SQL 为:
mysql> BEGIN PESSIMISTIC; Query OK, 0 rows affected (0.00 sec) mysql> select * from MANAGERS_UNIQUE where level=7 for update; +------------+------------+-----------+-------+ | MANAGER_ID | FIRST_NAME | LAST_NAME | LEVEL | +------------+------------+-----------+-------+ | 14273 | Brad7 | Craven7 | 7 | +------------+------------+-----------+-------+ 1 row in set (0.02 sec)
此时扫描 TIKV 所有的 LOCK 记录:
./tikv-ctl --data-dir tikv-0/data raw-scan --cf lock[2024/02/27 20:03:06.977 +08:00] [WARN] [config.rs:315] ["Create jemalloc nodump allocator for block cache failed: Not implemented: Not compiled with JEMALLOC, continue with default allocator"] [thread_id=0x4] key: "zt\200\000\000\000\000\000\000\377j_r\0011427\3773\000\000\000\374\000\000\000\374", value: "S*t\200\000\000\000\000\000\000j_r\00114273\000\000\000\374\201\200\260\344\357\270\352\233\006\252\256\003f\0067\251\311 \370\000\001c\0067\251\311 \370\000\002l\0067\251\306\253L\000\002\001" Total scanned keys: 1
普通索引命中的情况下,也只对 RowID 进行加锁。
执行的 SQL 为:
mysql> BEGIN PESSIMISTIC; Query OK, 0 rows affected (0.00 sec) mysql> select * from MANAGERS_UNIQUE where LAST_NAME='Craven7' FOR UPDATE; +------------+------------+-----------+ | MANAGER_ID | FIRST_NAME | LAST_NAME | +------------+------------+-----------+ | 14273 | Brad7 | Craven7 | +------------+------------+-----------+ 1 row in set (0.01 sec)
此时扫描 TIKV 所有的 LOCK 记录:
./tikv-ctl --data-dir tikv-0/data raw-scan --cf lock key: "zt\200\000\000\000\000\000\000\377j_r\0011427\3773\000\000\000\374\000\000\000\374", value: "S*t\200\000\000\000\000\000\000j_r\00114273\000\000\000\374\207\200\220\330\230\261\352\233\006\260\347\002f\0067\251\212\204<\000\001c\0067\251\212\204<\000\002l\0067\244\235?\264\000\002\001" Total scanned keys: 1
无索引命中的情况下,也只对 RowID 进行加锁。
mysql> BEGIN PESSIMISTIC; Query OK, 0 rows affected (0.00 sec) mysql> select * from MANAGERS_UNIQUE where MANAGER_ID=14275 FOR UPDATE; Empty set (0.00 sec)
此时扫描 TIKV 所有的 LOCK 记录:
./tikv-ctl --data-dir tikv-0/data raw-scan --cf lock key: "zt\200\000\000\000\000\000\000\377j_r\0011427\3775\000\000\000\374\000\000\000\374", value: "S*t\200\000\000\000\000\000\000j_r\00114275\000\000\000\374\201\200\360\306\231\335\362\233\006\333\267\003f\0067\312\351\230\334\000\001c\0067\312\351\230\334\000\002l\0067\301ja\344\000\002\001" Total scanned keys: 1
RR 隔离级别下,命中点查,即使没有数据,也会对点查的唯一索引或者 RowID 进行加锁。
可以看做弱化的间隙锁。
mysql> BEGIN PESSIMISTIC; Query OK, 0 rows affected (0.00 sec) mysql> select * from MANAGERS_UNIQUE where LEVEL=9; Empty set (0.01 sec)
此时扫描 TIKV 所有的 LOCK 记录:
./tikv-ctl --data-dir tikv-0/data raw-scan --cf lock Total scanned keys: 0
未命中点查情况下,没有数据情况下,TIKV 是不加锁的。
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; Query OK, 0 rows affected (0.00 sec) mysql> BEGIN PESSIMISTIC; Query OK, 0 rows affected (0.00 sec) mysql> select * from MANAGERS_UNIQUE where MANAGER_ID=14275 FOR UPDATE; Empty set (0.00 sec)
此时扫描 TIKV 所有的 LOCK 记录:
./tikv-ctl --data-dir tikv-0/data raw-scan --cf lock Total scanned keys: 0
RR 隔离级别下,命中点查,即使没有数据,也会对点查的唯一索引或者 RowID 进行加锁。
可以看做弱化的间隙锁。
mysql> BEGIN PESSIMISTIC; Query OK, 0 rows affected (0.00 sec) mysql> select * from MANAGERS_UNIQUE where LEVEL=9; Empty set (0.01 sec)
此时扫描 TIKV 所有的 LOCK 记录:
./tikv-ctl --data-dir tikv-0/data raw-scan --cf lock Total scanned keys: 0
RR 级别下,无论是否命中点查,没有数据,TIKV 是不加锁的。
执行的 SQL 为:
mysql> BEGIN PESSIMISTIC; Query OK, 0 rows affected (0.01 sec) mysql> select * from MANAGERS_UNIQUE where MANAGER_ID > 0 for update; +------------+------------+-----------+-------+ | MANAGER_ID | FIRST_NAME | LAST_NAME | LEVEL | +------------+------------+-----------+-------+ | 14273 | Brad7 | Craven7 | 7 | | 14274 | Brad8 | Craven8 | 8 | +------------+------------+-----------+-------+ 2 rows in set (0.02 sec)
此时扫描 TIKV 所有的 LOCK 记录:
./tikv-ctl --data-dir tikv-0/data raw-scan --cf lock key: "zt\200\000\000\000\000\000\000\377j_r\0011427\3773\000\000\000\374\000\000\000\374", value: "S*t\200\000\000\000\000\000\000j_r\00114273\000\000\000\374\201\200\200\355\321\363\352\233\006\264\256\001f\0067\253\235BX\000\001c\0067\253\235BX\000\002l\0067\251\306\253L\000\002\001" key: "zt\200\000\000\000\000\000\000\377j_r\0011427\3774\000\000\000\374\000\000\000\374", value: "S*t\200\000\000\000\000\000\000j_r\00114273\000\000\000\374\201\200\200\355\321\363\352\233\006\264\256\001f\0067\253\235BX\000\001c\0067\253\235BX\000\002l\0067\253\223\264<\000\002\001" Total scanned keys: 2
命中多条数据的情况下,每条数据的 RowID 将会被加锁。
特别的,如果使用 SQL:
mysql> BEGIN PESSIMISTIC; Query OK, 0 rows affected (0.00 sec) mysql> select * from MANAGERS_UNIQUE where FIRST_NAME in ("Brad7","Brad8") for update; +------------+------------+-----------+-------+ | MANAGER_ID | FIRST_NAME | LAST_NAME | LEVEL | +------------+------------+-----------+-------+ | 14273 | Brad7 | Craven7 | 7 | | 14274 | Brad8 | Craven8 | 8 | +------------+------------+-----------+-------+ 2 rows in set (0.01 sec)
也就是使用唯一索引的 WHERE IN 来搜索的话,此时扫描 TIKV 所有的 LOCK 记录:
./tikv-ctl --data-dir tikv-0/data raw-scan --cf lock key: "zt\200\000\000\000\000\000\000\377j_i\200\000\000\000\000\377\000\000\002\001Brad\3777\000\000\000\374\000\000\000\374", value: "S:t\200\000\000\000\000\000\000j_i\200\000\000\000\000\000\000\002\001Brad7\000\000\000\374\202\200\240\252\265\367\352\233\006\310\302\002f\0067\253\273UH\000\002c\0067\253\273UH\000\003l\0067\244\235?\264\000\002\002" key: "zt\200\000\000\000\000\000\000\377j_i\200\000\000\000\000\377\000\000\002\001Brad\3778\000\000\000\374\000\000\000\374", value: "S:t\200\000\000\000\000\000\000j_i\200\000\000\000\000\000\000\002\001Brad7\000\000\000\374\202\200\240\252\265\367\352\233\006\310\302\002f\0067\253\273UH\000\002c\0067\253\273UH\000\003l\0067\253\223\264<\000\002\001" key: "zt\200\000\000\000\000\000\000\377j_r\0011427\3773\000\000\000\374\000\000\000\374", value: "S:t\200\000\000\000\000\000\000j_i\200\000\000\000\000\000\000\002\001Brad7\000\000\000\374\202\200\240\252\265\367\352\233\006\313\302\002f\0067\253\273UH\000\002c\0067\253\273UH\000\003l\0067\251\306\253L\000\002\001" key: "zt\200\000\000\000\000\000\000\377j_r\0011427\3774\000\000\000\374\000\000\000\374", value: "S:t\200\000\000\000\000\000\000j_i\200\000\000\000\000\000\000\002\001Brad7\000\000\000\374\202\200\240\252\265\367\352\233\006\313\302\002f\0067\253\273UH\000\002c\0067\253\273UH\000\003l\0067\253\223\264<\000\002\001" Total scanned keys: 4
虽然命中了 2 行数据,但是却对 4 个 KEY 进行了加锁。分别是每行数据的 RowID 和 唯一索引。
上述场景总结如下:
Result 行数 | LOCK 数量 | RowID锁 | 唯一键锁 | SQL | |||
---|---|---|---|---|---|---|---|
主键搜索 | 1 | 1 | 1 | 0 | select * from MANAGERS_UNIQUE where MANAGER_ID=14274 for update; | ||
唯一索引搜索 | 1 | 2 | 1 | 1 | select * from MANAGERS_UNIQUE where FIRST_NAME='Brad7' for update; | ||
普通索引搜索 | 1 | 1 | 1 | 0 | select * from MANAGERS_UNIQUE where level=7 for update; | ||
无索引搜索 | 1 | 1 | 1 | 0 | select * from MANAGERS_UNIQUE where LAST_NAME='Craven7' for update; | ||
命中多条数据 | 2 | 2 | 2 | 0 | select * from MANAGERS_UNIQUE where MANAGER_ID IN (14273, 14274) for update; | ||
命中多条数据+唯一键搜索 | 2 | 4 | 2 | 2 | select * from MANAGERS_UNIQUE where FIRST_NAME IN ('Brad7' , 'Brad8' ) for update; |
如果 SELECT SQL 未能命中数据的话,情况就比较复杂了:
Result 行数 | LOCK 数量 | RowID锁 | 唯一键锁 | SQL | |
---|---|---|---|---|---|
RR + 点查 | 0 0 | 1 1 | 1 0 | 0 1 | select * from MANAGERS_UNIQUE where MANAGER_ID=999999 for update; select * from MANAGERS_UNIQUE where FIRST_NAME='Brad9999' for update; |
RR + 普通查 | 0 | 0 | 0 | 0 | select * from MANAGERS_UNIQUE where LAST_NAME='Craven9' for update; |
RC + 点查 | 0 | 0 | 0 | 0 | select * from MANAGERS_UNIQUE where MANAGER_ID=999999 for update; |
RC + 普通查 | 0 | 0 | 0 | 0 | select * from MANAGERS_UNIQUE where LAST_NAME='Craven9' for update; |
我们发现一个比较奇怪的现象,如果 SQL
语句的 WHERE
语句命中了唯一索引的点查,那么 TIDB
将会对唯一索引也加锁。其他情况下,不会对唯一索引进行加锁。(RC
级别下,未命中数据的话,还比较特殊, 唯一索引 / RowID
不加锁)
那么 SELECT FOR UPDATE
语句是否有必要对唯一索引加锁呢?
个人认为好像并不需要,因为 RowID 已经被加锁,所以被锁定的行数据不会被更新。
同时其他行数据是否可以更新唯一键呢?也不会。例如:
+------------+------------+-----------+-------+ | 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
" 的唯一索引后,会直接返回错误。因此好像并不需要对唯一键进行加锁就可以实现这个功能。(实际上,悲观锁的接口参数 should_not_exist
就是这个作用)
目前本人未能想出 SELECT FOR UPDATE
语句需要对唯一索引加锁的必要场景。
执行的 SQL 为:
mysql> BEGIN PESSIMISTIC; Query OK, 0 rows affected (0.00 sec) mysql> UPDATE MANAGERS_UNIQUE SET LAST_NAME='Craven9' where LAST_NAME='Craven8'; Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings: 0
此时扫描 TIKV
所有的 LOCK
记录:
./tikv-ctl --data-dir tikv-0/data raw-scan --cf lock key: "zt\200\000\000\000\000\000\000\377j_i\200\000\000\000\000\377\000\000\002\001Brad\3778\000\000\000\374\000\000\000\374", value: "S*t\200\000\000\000\000\000\000j_r\00114274\000\000\000\374\201\200\360\331\241\217\353\233\006\320\254\004f\0067\254};<\000\001c\0067\254};<\000\002l\0067\254K-\230\000\002\001" key: "zt\200\000\000\000\000\000\000\377j_r\0011427\3774\000\000\000\374\000\000\000\374", value: "S*t\200\000\000\000\000\000\000j_r\00114274\000\000\000\374\201\200\360\331\241\217\353\233\006\314\254\004f\0067\254};<\000\001c\0067\254};<\000\002l\0067\254K-\230\000\002\001" Total scanned keys: 2
因此普通的 UPDATE
语句也会对 RowID
和 唯一索引 加锁。
同样的疑问:这个唯一索引 Value (Brad7
) 的加锁,是否是 必要 的呢?
执行的 SQL 为:
+------------+------------+-----------+-------+ | MANAGER_ID | FIRST_NAME | LAST_NAME | LEVEL | +------------+------------+-----------+-------+ | 14273 | Brad7 | Craven7 | 7 | | 14274 | Brad8 | Craven8 | 8 | +------------+------------+-----------+-------+ mysql> BEGIN PESSIMISTIC; Query OK, 0 rows affected (0.00 sec) mysql> UPDATE MANAGERS_UNIQUE SET MANAGER_ID=14275 where MANAGER_ID=14273; Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings: 0
此时扫描 TIKV
所有的 LOCK
记录:
./tikv-ctl --data-dir tikv-0/data raw-scan --cf lock key: "zt\200\000\000\000\000\000\000\377j_i\200\000\000\000\000\377\000\000\002\001Brad\3777\000\000\000\374\000\000\000\374", value: "S*t\200\000\000\000\000\000\000j_r\00114273\000\000\000\374\201\200\260\200\360\223\353\233\006\307\202\002f\0067\254\237\314\274\000\001c\0067\254\237\314\274\000\002l\0067\244\235?\264\000\002\002" key: "zt\200\000\000\000\000\000\000\377j_r\0011427\3773\000\000\000\374\000\000\000\374", value: "S*t\200\000\000\000\000\000\000j_r\00114273\000\000\000\374\201\200\260\200\360\223\353\233\006\304\202\002f\0067\254\237\314\274\000\001c\0067\254\237\314\274\000\002l\0067\251\306\253L\000\002\001" key: "zt\200\000\000\000\000\000\000\377j_r\0011427\3775\000\000\000\374\000\000\000\374", value: "S*t\200\000\000\000\000\000\000j_r\00114273\000\000\000\374\201\200\260\200\360\223\353\233\006\307\202\002f\0067\254\237\314\274\000\001c\0067\254\237\314\274\000\002l\000\000\000\000\000\000\000\000\001" Total scanned keys: 3
如果更新的是主键,那么新主键 Value 和旧主键的 Value 都需要被加锁。
同时还要对唯一索引进行加锁。
执行的 SQL 为:
BEGIN PESSIMISTIC; Query OK, 0 rows affected (0.00 sec) mysql> UPDATE MANAGERS_UNIQUE SET FIRST_NAME='Brad9' where FIRST_NAME='Brad7'; Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings:
此时扫描 TIKV
所有的 LOCK
记录:
./tikv-ctl --data-dir tikv-0/data raw-scan --cf lock key: "zt\200\000\000\000\000\000\000\377j_i\200\000\000\000\000\377\000\000\002\001Brad\3777\000\000\000\374\000\000\000\374", value: "S:t\200\000\000\000\000\000\000j_i\200\000\000\000\000\000\000\002\001Brad7\000\000\000\374\201\200\360\254\332\224\353\233\006\257\306\003f\0067\254\245\245\234\000\001c\0067\254\245\245\234\000\002l\0067\244\235?\264\000\002\002" key: "zt\200\000\000\000\000\000\000\377j_i\200\000\000\000\000\377\000\000\002\001Brad\3779\000\000\000\374\000\000\000\374", value: "S:t\200\000\000\000\000\000\000j_i\200\000\000\000\000\000\000\002\001Brad7\000\000\000\374\201\200\360\254\332\224\353\233\006\270\306\003f\0067\254\245\245\234\000\001c\0067\254\245\245\234\000\002l\000\000\000\000\000\000\000\000\001" key: "zt\200\000\000\000\000\000\000\377j_r\0011427\3773\000\000\000\374\000\000\000\374", value: "S:t\200\000\000\000\000\000\000j_i\200\000\000\000\000\000\000\002\001Brad7\000\000\000\374\201\200\360\254\332\224\353\233\006\264\306\003f\0067\254\245\245\234\000\001c\0067\254\245\245\234\000\002l\0067\251\306\253L\000\002\001" Total scanned keys: 3
如果更新的是唯一索引,那么新唯一索引 Value 和旧唯一索引的 Value 都需要被加锁。
同时还需要对 RowID 进行加锁。
对新的唯一索引 Value
加锁这是必要的,例如如下场景:
+------------+------------+-----------+-------+ | MANAGER_ID | FIRST_NAME | LAST_NAME | LEVEL | +------------+------------+-----------+-------+ | 14273 | Brad7 | Craven7 | 7 | | 14274 | Brad8 | Craven8 | 8 | +------------+------------+-----------+-------+ 事务一: mysql> BEGIN PESSIMISTIC; mysql> UPDATE MANAGERS_UNIQUE SET FIRST_NAME='Brad9' where FIRST_NAME='Brad7'; +------------+------------+-----------+-------+ | MANAGER_ID | FIRST_NAME | LAST_NAME | LEVEL | +------------+------------+-----------+-------+ | 14273 | Brad7 | Craven7 | 7 | +------------+------------+-----------+-------+ 事务二: mysql> BEGIN PESSIMISTIC; mysql> UPDATE MANAGERS_UNIQUE SET FIRST_NAME='Brad9' where FIRST_NAME='Brad8';
对 RowID
主键进行加锁基本是每个 DML 语句必须操作。
如果数据表存在唯一索引的话,还需要根据各种场景对唯一索引进行加锁。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。