Today I will introduce to you the knowledge related to locks in MySQL.
Unless otherwise stated, this article uses the default InnoDB engine. If other engines or databases are involved, they will be specifically pointed out.
A lock is a method used to ensure that each transaction can still read and modify data in a consistent manner in a concurrent scenario. After the data is locked, other transactions cannot modify it or can only block and wait for the lock to be released. Therefore, the granularity of the lock can affect the performance of accessing the database to a certain extent.
In terms of lock granularity, we can divide locks into table locks and row locks.
As the name suggests, table lock is to directly lock the table. In the MyISAM engine, there is only table lock.
The locking method of table lock is:
LOCK TABLE 表名 READ;--锁定后表只读 UNLOCK TABLE; --解锁复制代码
Row lock, from the name, is to lock a row of data. However, the actual implementation of row lock The algorithm will be relatively complex, and sometimes it does not just lock a certain piece of data. This will be expanded upon later.
The normal idea is: after locking a row of data, other transactions cannot access this data. Then we imagine that if transaction A accesses a piece of data, it just takes it out to read and does not want to modify it. , it happens that transaction B also comes to access this data, and it just wants to take it out and read it, and does not want to modify it. If it is blocked at this time, it will be a bit of a waste of performance. Therefore, in order to optimize this data reading scenario, we divide row locks into two major types:Shared locks and exclusive locks.
Shared lock, Shared Lock, also known as read lock, S lock, means that after a piece of data is added with an S lock, other transactions can also read the data. Share a lock.
We can add shared locks through the following statement:
select * from test where id=1 LOCK IN SHARE MODE;复制代码
After locking, the lock will be released until the locked transaction ends (commit or rollback).
Exclusive lock, Exclusive Lock, also known as write lock, X lock. That is to say, after an X lock is added to a piece of data, other transactions that want to access this data can only block and wait for the lock to be released, which is exclusive.
When we modify data, such as: insert, update, delete, MySQL will automatically add an exclusive lock. Similarly, we can manually add an exclusive lock through the following sql statement:
select * from test where id=1 for update;复制代码
In the InnoDB engine, row locks and table locks are allowed to coexist.
But there will be a problem. If transaction A locks one row of data in table t, and transaction B wants to lock table t at this time, what should we do at this time? How does transaction B know whether there is a row lock in table t? If full table traversal is used, when the data in the table is large, it will take half a day to lock, so MySQL introducesintention lock.
Intention lock is a table lock, which is divided into two types: Intention Shared Lock and Intention Exclusive Lock. The locks can be referred to as IS locks and IX locks respectively.
Intention locks are maintained by MySQL itself, and users cannot manually add intentions.
There are two major locking rules for intention locks:
In this case, the above problem will be easily solved. When you need to lock a table, you only need to check whether the table has a corresponding intention lock. There is no need to traverse the entire table. surface.
The picture below shows the compatibility of various locks. Please refer to the official website:
建立以下两张表,并初始化5条数据,注意test表有2个索引而test2没有索引:
CREATE TABLE `test` ( `id` int(11) NOT NULL, `name` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`), KEY `NAME_INDEX` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO test VALUE(1,'张1'); INSERT INTO test VALUE(5,'张5'); INSERT INTO test VALUE(8,'张8'); INSERT INTO test VALUE(10,'张10'); INSERT INTO test VALUE(20,'张20'); CREATE TABLE `test2` ( `id` varchar(32) NOT NULL, `name` varchar(32) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO test2 VALUE(1,'张1'); INSERT INTO test2 VALUE(5,'张5'); INSERT INTO test2 VALUE(8,'张8'); INSERT INTO test2 VALUE(10,'张10'); INSERT INTO test2 VALUE(20,'张20');复制代码
在行锁中,假如我们对一行记录加锁,那么到底是把什么东西锁住了,我们来看下面两个例子:
举例1(操作test表):
X | IX | S | IS | |
---|---|---|---|---|
X | Mutual Exclusion |
Mutual Exclusion |
Mutual Exclusion |
Mutual Exclusion |
IX | Mutual exclusion |
Sharing |
Conflict |
##Sharing |
Mutual exclusion | Mutual Exclusion | Sharing | Sharing | |
Mutual Exclusion | Sharing | Sharing | Sharing |
事务A | 事务B |
---|---|
BEGIN; | |
SELECT * FROM test WHERE id=1 FOR UPDATE; | |
SELECT * FROM test WHERE id=1 FOR UPDATE; 阻塞 |
|
SELECT * FROM test WHERE id=5 FOR UPDATE; 加锁成功 |
|
COMMIT; (释放锁) |
|
SELECT * FROM test WHERE id=1 FOR UPDATE; 加锁成功 |
举例2(操作test2表):
事务A | 事务B |
---|---|
BEGIN; | |
SELECT * FROM test2 WHERE id=1 FOR UPDATE; | |
SELECT * FROM test2 WHERE id=1 FOR UPDATE; 阻塞 |
|
SELECT * FROM test2 WHERE id=5 FOR UPDATE; 阻塞 |
|
COMMIT; (释放锁) |
|
SELECT * FROM test2 WHERE id=1 FOR UPDATE; 加锁成功 |
从上面两个例子我们可以发现,test表好像确实是锁住了id=1这一行的记录,而test2表好像不仅仅是锁住了id=1这一行记录,实际上经过尝试我们就知道,test2表是被锁表了,所以其实MySQL中InnoDB锁住的是索引,当没有索引的时候就会锁表。
接下来再看一个场景:
事务A | 事务B |
---|---|
BEGIN; | |
SELECT * FROM test WHERE name=‘张1’ FOR UPDATE; | |
SELECT name FROM test WHERE name=‘张1’ FOR UPDATE; 阻塞 |
|
SELECT id FROM test WHERE id=1 FOR UPDATE; 阻塞 |
|
COMMIT; (释放锁) |
|
SELECT id FROM test WHERE id=1 FOR UPDATE; 加锁成功 |
这个例子中我们是把name索引锁住了,然后我们在事务B中通过主键索引只查id,这样就用到name索引了,但是最后发现也被阻塞了。所以我们又可以得出下面的结论,MySQL索引不但锁住了辅助索引,还会把辅助索引对应的主键索引一起锁住。
到这里,可能有人会有怀疑,那就是我把辅助索引锁住了,但是假如加锁的时候,只用到了覆盖索引,然后我再去查主键会怎么样呢?
接下来让我们再验证一下:
事务A | 事务B |
---|---|
BEGIN; | |
SELECT name FROM test WHERE name=‘张1’ FOR UPDATE; | |
SELECT name FROM test WHERE name=‘张1’ FOR UPDATE; 阻塞 |
|
SELECT * FROM test WHERE id=1 FOR UPDATE; 阻塞 |
|
SELECT id FROM test WHERE id=1 FOR UPDATE; 阻塞 |
|
COMMIT; (释放锁) |
|
SELECT id FROM test WHERE id=1 FOR UPDATE; 加锁成功 |
We can see that even if only auxiliary index locking is used, MySQL will still lock the primary key index, and the B tree leaf node of the primary key index stores the entire data, so any field queried will be locked. .
At this point, we can clearly draw a conclusion on what the lock is:
In the InnoDB engine, what is locked is the index:
We mentioned when introducing transactions in the previous article that MySQL prevents phantom reading by locking, but if the row lock only locks a row of records, it does not seem to prevent phantom reading. , so row lock locking a record is only one of the cases. In fact, there are three row lock algorithms: Record Lock, Gap Lock and Next-Key Lock. The reason why it can Preventing phantom reading is exactly what the key lock does.
The record lock is introduced above. When our query can hit a record, InnoDB will use the record lock to lock the hit row of records. .
When our query does not hit the record, InnoDB will add a gap lock at this time.
TransactionA | TransactionB |
---|---|
Blocking | |
##INSERT INTO test VALUE (3,'Zhang 3'); | Blocking
|
#SELECT * FROM test WHERE id=2 FOR UPDATE; Lock successful |
COMMIT; |
There is no conflict between gap locks and gap locks, that is, transaction A adds gap locks, and transaction B adds gap locks. Gap locks can be added to the same gap.
(The reason why gap lock is used is when there is no data hit, so there is no need to block reading, and there is no need to block other transactions from locking the same gap)
Next-Key Lock
Next-Key Lock is a combination of record lock and gap lock. When we perform a range query and not only hit one or more records, but also include gaps, temporary key locking will be used. Keyless locking is the default algorithm for row locks in InnoDB.
Note that this is only for the RR isolation level. For the RC isolation level, in addition to foreign key constraints and uniqueness constraints, gap locks will be added. Without gap locks, naturally there will be no temporary key locks, so The row locks added at the RC level are all record locks. If no record is hit, no locks will be locked. Therefore, the RC level does not solve the problem of phantom reading. The temporary key lock will be downgraded to a gap lock or a record lock under the following two conditions:When the query misses the task record, it will be downgraded to a gap lock.
When a record is hit using the primary key or unique index, it will be downgraded to a record lock.
BEGIN; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
SELECT * FROM test WHERE id>=2 AND id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
##INSERT INTO test VALUE (6,'Zhang 6'); | Blocking||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
##INSERT INTO test VALUE (8,'Zhang 8'); Blocking |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SELECT * FROM test WHERE id=8 FOR UPDATE; Blocking |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
INSERT INTO test VALUE (9,'Zhang 9'); Insertion successful |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
##COMMIT; (release lock) |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
上面这个例子,事务A加的锁跨越了(1,5)和(5,8)两个间隙,且同时命中了5,然后我们发现我们对id=8这条数据进行操作也阻塞了,但是9这条记录插入成功了。 临键锁加锁规则临键锁的划分是按照左开右闭的区间来划分的,也就是我们可以把test表中的记录划分出如下区间:(-∞,1],(1,5],(5,8],(8,10],(10,20],(20,+∞)。 那么临键锁到底锁住了哪些范围呢? **临键锁中锁住的是最后一个命中记录的 key 和其下一个左开右闭的区间** 那么上面的例子中其实锁住了(1,5]和(5,8]这两个区间。 临键锁为何能解决幻读问题临键锁为什么要锁住命中记录的下一个左开右闭的区间?答案就是为了解决幻读。 我们想一想上面的查询范围id>=2且id 当然,其实如果我们执行的查询刚好是id>=2且id 在我们使用锁的时候,有一个问题是需要注意和避免的,我们知道,排它锁有互斥的特性。一个事务持有锁的时候,会阻止其他的事务获取锁,这个时候会造成阻塞等待,那么假如事务一直等待下去,就会一直占用CPU资源,所以,锁等待会有一个超时时间,在InnoDB引擎中,可以通过参数:innodb_lock_wait_timeout查询: SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';复制代码 默认超时时间是50s,超时后会自动释放锁回滚事务。但是我们想一下,假如事务A在等待事务B释放锁,而事务B又在等待事务A释放锁,这时候就会产生一个等待环路了,而这种情况是无论等待多久都不可能会获取锁成功的,所以是没有必要去等50s的,这种形成等待环路的现象又叫做死锁。 死锁(Dead Lock)什么是死锁死锁是指的两个或者两个以上的事务在执行过程中,因为争夺锁资源而造成的一种互相等待的现象。
Deadlock detectionCurrently, most databases use the wait-for graph (wait graph) method to detect deadlocks. The InnoDB engine also uses this method to detect deadlocks. Two types of information will be recorded in the database:
Avoidance of deadlock
Lock information queryInnoDB provides 3 tables under the information_schema library for us to query and troubleshoot transaction and lock-related issues . INNODB_TRXRecords information about each transaction currently executed in InnoDB, including whether the transaction is waiting for a lock, when the transaction started, and the SQL statement the transaction is executing (if any) ).
Records information about each lock that a transaction requested but did not obtain, and each transaction that held a lock but was blocking another transaction. lock information.
|
The above is the detailed content of Understand what a lock is and how to solve the phantom read problem in MySQL. For more information, please follow other related articles on the PHP Chinese website!