Whether select for update is a row lock or a table lock, it really depends on the situation

background

I have seen many select for updatearticles about whether to write row locks or table locks, but the conclusions of each article seem to be different. At the same time, the issue of row locks or table locks directly affects the performance of the system, so I made a special research for you, and this article has summarized and verified the conclusions in 20 scenarios for you.

For software or frameworks, especially in the case of major version updates, conclusions from specific versions are often meaningless. In response to this question, the main reason why there are multiple versions of answers on the Internet is to break away from the version of MySQL and the transaction isolation level.

This article is based on two MySQL versions (5.7.x, 8.0.x) and two common transaction isolation levels (read committed, repeatable read) to verify one by one. There are a total of four categories of situations and 20 small scenes. Finally, I will summarize a conclusive verification result for you. Everyone can bookmark it, and it has been reserved for later reference.

By reading this article, you can not only learn relevant conclusions, but also provide a set of scientific experimental methodology, which I personally think is more important to everyone.

Environmental preparation

Before verification, we first prepare the specific environment and data.

Create table statement:

CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_no` varchar(16) DEFAULT NULL COMMENT '用户编号',
  `user_name` varchar(16) DEFAULT NULL COMMENT '用户名',
  `age` int(3) DEFAULT NULL COMMENT '年龄',
  `address` varchar(128) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_idx_user_no` (`user_no`),
  KEY `idx_user_name` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

Initialization data:

insert into user values(null,'0001','user01',18,'北京');
insert into user values(null,'0002','user02',19,'上海');
insert into user values(null,'0003','user03',20,'广州');
insert into user values(null,'0004','user04',21,'深圳');
insert into user values(null,'0005','user05',22,'杭州');

Database version:

版本一:
>select @@version;
5.7.22

版本二:
>select @@version;
8.0.18

Query data transaction isolation level:

>select @@transaction_isolation;
REPEATABLE-READ

Four transaction isolation levels supported by MySQL innodb:

  • READ_UNCOMMITTED: read uncommitted;
  • READ_COMMITTED: read committed, hereinafter referred to as RC;
  • REPEATABLE_READ: Repeatable read, MySQL's default transaction isolation level. Hereinafter referred to as RR;
  • SERIALIZABLE: serial read;

Set the global isolation level:

set global transaction isolation level REPEATABLE READ;
set global transaction isolation level READ COMMITTED;

Set the session isolation level:

set session transaction isolation level REPEATABLE READ;
set session transaction isolation level READ COMMITTED;

Turn off autocommit:

> set @@autocommit=0;  //设置自动提交关闭

After the lock statement is executed, committhe command can be executed to commit the transaction.

commit;

After preparing the above data, you can start the verification of each scene. Each scenario is assigned a number, for example: V5.x-RR-primary key , which means that in MySQL 5.7.x, the transaction isolation level is RR (repeatable read), and the condition field is the primary key.

Scenario 1.1: V5.x-RR-primary key

Operation : Use the primary key ID as a conditional query, and then start a new transaction to update the data.

Analysis ideas : 1. If the update data is blocked, it means that the lock is successful; 2. If the update of other data is successful, it means that it is a row lock. If the update of other data fails, it means that it is a table lock. Third, insert operations will be tested in some scenarios; all subsequent operations are basically the same.

Execute pessimistic lock query:

select * from user where id = 1 for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

In this scenario, let's take a look at what locks are added to the database.

When the second statement is blocked, execute the view lock information statement:

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;

Note that the above statement can query data only if the second statement is being executed and the second statement is blocked.

The query results are as follows:

lock information

The second record is a for update lock table statement, and the first record is a simple update statement. It can be seen that in this scenario, lock_mode is X, lock_type is RECORD, and lock_data is 1.

lock_mode is X (exclusive lock): that is, a write lock, which allows transactions that obtain exclusive locks to update data, and prevents other transactions from obtaining shared read locks and exclusive write locks of the same data set.

lock_type is RECORD, which means it is a row-level lock, and lock_data means that 1 record is locked.

Perform an update other record operation:

update user set age = age +1 where id = 2;

execution succeed.

Conclusion : When the query condition is the primary key, select for updateit is a row-level lock.

After we execute a scene, we need to execute committhe command to submit the current thing.

Scenario 1.2: V5.x-RR-unique index

Perform pessimistic locking operations:

select * from user where user_no = '0001' for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

Query lock information, which is consistent with the primary key in Scenario 1.

Perform an update other record operation:

update user set age = age +1 where id = 2;

execution succeed.

Conclusion : When the query condition is a unique index, select for updateit is a row-level lock.

Scenario 1.3: V5.x-RR-Common Index

Perform pessimistic locking operations:

select * from user where user_name = 'user01' for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

Query lock information:

lock information

At this time, the lock type is not only an X exclusive lock, but also a GAP (gap lock), that is to say, an exclusive gap lock is added for the data.

Perform an update other record operation:

update user set age = age +1 where id = 2;

execution succeed.

At this time, perform another insert operation:

insert into user values(null,'0006','user05',23,'重庆');

execution succeed.

Since there is a gap lock, perform another insert operation with the same user_name as the query condition:

insert into user values(null,'0008','user01',24,'成都');

Execution is blocked, indicating that there is an exclusive gap lock at this time.

Conclusion : When the query condition is an ordinary index, select for updateit is a row-level lock, and there will be an exclusive gap lock at the same time. When the inserted data meets the query conditions of the lock statement (equality, range, etc.), blocking will occur.

Scenario 1.4: V5.x-RR-No Index

Perform pessimistic locking operations:

select * from user where address = '北京' for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

Perform an update other record operation:

update user set age = age +1 where id = 2;

Execution is blocked .

At this time, the query lock table information is displayed as follows:

lock information

The strange thing here is the lock_type. Obviously, the above lock operation has locked the entire table, but the lock_type is still RECORD. The source is a bit puzzling for now.

Conclusion : When the query condition has no index, select for updateit is a table-level lock.

Scenario 1.5: V5.x-RR-index-range query

Perform pessimistic locking operations:

select * from user where id > 1 for update;

Perform an update operation:

update user set age = age +1 where id = 1;

If the execution is successful, it means that the record with id 1 is not locked.

Perform an insert operation:

insert into user values(null,'0007','user07',24,'武汉');

The insert operation is blocked. This is because the id generated by the inserted data meets the condition greater than 1 and will be blocked.

The information is as follows:

lock information

At this time, although lock_type is RECORD, lock_data displays supremum pseudo-record, which is the next-key lock (Next-key Lock) used by InnoDB to solve the problem of phantom reading. Here, the gap lock and the next-key lock can be regarded as the same.

It should be noted that the supremum pseudo-record may be a gap lock, which needs to be judged in conjunction with the heap no in the deadlock log. Heap no 1 is a gap lock.

Conclusion : When the query condition has an index and the query condition is a range, select for updatea gap lock or a key lock will be used to lock the data within the specified range. Of course, when the query condition has no index, it is consistent with scenario 1.4, which is a table lock.

Scenario 2.1: V8.x-RR-primary key

Execute pessimistic lock query:

select * from user where id = 1 for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

View the locks corresponding to the database:

SELECT * FROM performance_schema.data_locks;

Note that in MySQL 8, performance_schema is used to replace the INFORMATION_SCHEMA-based lock query method in MySQL5.

lock information

In the above query results, there are two records. The lock_type field shows the lock range, and the lock_mode field shows the lock type. It can be seen that the SQL statement first adds an IX (intent exclusive lock, table lock) to the table range. Then, an X (exclusive lock) and a REC_NOT_GAP (row lock) are added to the record (Record) range. In combination, a row-level exclusive lock is added to this record, and other transactions cannot add any more to it. locked.

Here, since IX (intent exclusive lock) is added at the table level, why not a table lock? This is because the intentional exclusive lock is only the intentional lock of the table name. When other transactions want to lock the data of the whole table, there is no need to judge whether each piece of data is locked.

Before a transaction adds an exclusive lock to a row of records, it must first obtain the IX lock of the table. The intent exclusive locks are compatible with each other and can be parallelized without conflicts. The meaning of the intentional exclusive lock is to obtain table locks more efficiently. The main purpose is to show that the transaction is locking a certain row or trying to lock a certain row.

Continuing with the experiment, perform operations that update other records:

update user set age = age +1 where id = 2;

execution succeed.

Conclusion : When the query condition is the primary key, select for updateit is a row-level lock.

Scenario 2.2: V8.x-RR-unique index

Perform pessimistic locking operations:

select * from user where user_no = '0001' for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

Query lock information:

lock information

At this point, you can see three locks, one table-level IX lock, one row-level exclusive lock based on a unique index, and one row-level exclusive lock based on a primary key.

Perform an update other record operation:

update user set age = age +1 where id = 2;

execution succeed.

Conclusion : When the query condition is a unique index, select for updateit is a row-level lock.

Scenario 2.3: V8.x-RR-Common Index

Perform pessimistic locking operations:

select * from user where user_name = 'user01' for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

Query lock information:

lock information

At this point, you can see four locks, one table-level IX lock, one X exclusive lock based on ordinary indexes, one row-level exclusive lock based on primary keys, and one exclusive gap lock based on ordinary indexes X,GAP.

Perform an update other record operation:

update user set age = age +1 where id = 2;

If the execution is successful, it means that the update operation has no effect.

Since there is an exclusive gap lock, it is necessary to test another insertion operation at this time:

insert into user values(null,'0006','user05',23,'重庆');

execution succeed.

Perform another insert operation:

insert into user values(null,'0007','user01',24,'武汉');

Note that the record inserted here user_namehas the same conditions as the lock query, and the discovery operation is blocked.

It can be seen from the two insertion operations that the exclusive gap lock will block the insertion of data that meets the query condition (user_name='user01').

Conclusion : When the query condition is an ordinary index, select for updateit is a row-level lock, and an exclusive gap lock will be added at the same time. If the inserted data meets the query conditions of the lock statement (equal, range conditions, etc.), it cannot be inserted.

Scenario 2.4: V8.x-RR-No Index

Perform pessimistic locking operations:

select * from user where address = '北京' for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

Query lock information:

lock information

At this time, a total of 8 locks have been added to the database, one table-level IX intent exclusive lock, six X locks based on primary keys for data records (6 in total), and one supreme pseudo-record lock for records.

Perform an update other record operation:

update user set age = age +1 where id = 2;

Execution is blocked .

Conclusion : When the query condition has no index, select for updateit is a table-level lock.

Scenario 2.5: V8.x-RR-index-range query

Perform pessimistic locking operations:

select * from user where id > 1 for update;

Perform an update operation:

update user set age = age +1 where id = 1;

If the execution is successful, it means that the record with id 1 is not locked.

Perform an insert operation:

insert into user values(null,'0007','user07',24,'武汉');

The insert operation is blocked. This is because the id generated by the inserted data meets the condition greater than 1 and will be blocked.

The query lock information is as follows:

lock information

At this time, comparing the lock information with scenario 2.4, there is one less lock that does not meet the condition record (id=1), and other eligible data are locked.

Conclusion : When the query condition has an index and the query condition is a range, select for updatea gap lock or a key lock will be used to lock the data within the specified range.

After completing the above verification for the RR transaction isolation level, the following will switch the database transaction isolation level to RC.

set global transaction isolation level READ COMMITTED;

Note that the database may need to be restarted here. If the command configuration is invalid, it can be configured through the database configuration file and restarted.

In addition, you can also achieve the effect by executing sessionlevel settings in all command windows. After the settings are completed, please note that verification is required.

Scenario 3.1: V5.x-RC-primary key

Execute pessimistic lock query:

select * from user where id = 1 for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

Lock information is the same as for RR transactions.

Perform an update other record operation:

update user set age = age +1 where id = 2;

execution succeed.

Conclusion : When the query condition is the primary key, select for updateit is a row-level lock.

Scenario 3.2: V5.x-RC-Unique Index

Perform pessimistic locking operations:

select * from user where user_no = '0001' for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

Query lock information, consistent with RR.

Perform an update other record operation:

update user set age = age +1 where id = 2;

execution succeed.

Conclusion : When the query condition is a unique index, select for updateit is a row-level lock.

Scenario 3.3: V5.x-RC-Common Index

Perform pessimistic locking operations:

select * from user where user_name = 'user01' for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

The query lock information is as follows:

lock information

Then post the lock information in the RR scenario:

lock information

It can be seen that the RC transaction isolation level has one less GAP (gap lock) than the RR transaction isolation level.

Perform an update other record operation:

update user set age = age +1 where id = 2;

execution succeed.

At this time, perform another insert operation:

insert into user values(null,'0009','user01',24,'郑州');

execution succeed.

Then verify whether the gap lock really does not exist, and perform an insert operation with the same user_name as the query condition:

insert into user values(null,'0008','user01',24,'成都');

The execution is successful, indicating that the gap lock does not exist at this time.

Conclusion : When the query condition is an ordinary index, select for updateit is a row-level lock and no gap lock.

Scenario 3.4: V5.x-RC-No Index

Perform pessimistic locking operations:

select * from user where address = '北京' for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

The lock information is as follows:

lock information

The exclusive lock based on the primary key is displayed, which is quite unexpected, and no table lock is performed.

Perform an update other record operation:

update user set age = age +1 where id = 2;

execution succeed.

Perform another insert operation, and the inserted data is consistent with the query condition address:

insert into user values(null,'0011','user01',24,'北京');

execution succeed.

Conclusion : When the query condition has no index, select for updateit is a row-level lock. That is to say, under the RC transaction isolation level, even if there is no index, only the record is locked, which is different from the usual intuition.

Reason : The reason for the above situation is that if there is no index on the lock condition, MySQL will use the cluster (primary key) index to scan and filter the entire table, and each record will be added with an X lock. But for the sake of efficiency, MySQL will unlock the records that do not meet the conditions during the scanning process.

Scenario 3.5: V5.x-RC-index-range query

Perform pessimistic locking operations:

select * from user where id > 1 for update;

Perform an update operation:

update user set age = age +1 where id = 1;

If the execution is successful, it means that the record with id 1 is not locked.

Perform an update operation:

update user set age = age +1 where id = 2;

The operation is blocked. This is because the id of the operated data meets the condition greater than 1 and will be blocked.

The information is as follows:

lock information

Conclusion : When the query condition has an index and the query condition is a range, select for updatelock the data in the specified range.

Scenario 4.1: V8.x-RC-Primary Key

Execute pessimistic lock query:

select * from user where id = 1 for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

The lock information is the same as that of RR.

Continuing with the experiment, perform operations that update other records:

update user set age = age +1 where id = 2;

execution succeed.

Conclusion : When the query condition is the primary key, select for updateit is a row-level lock.

Scenario 4.2: V8.x-RC-Unique Index

Perform pessimistic locking operations:

select * from user where user_no = '0001' for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

The lock information is the same as that of RR.

Perform an update other record operation:

update user set age = age +1 where id = 2;

execution succeed.

Conclusion : When the query condition is a unique index, select for updateit is a row-level lock.

Scenario 4.3: V8.x-RC-Common Index

Perform pessimistic locking operations:

select * from user where user_name = 'user01' for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

Query lock information:

lock information

Compare the lock information in the RR scenario:

lock information

It can be seen that a row-level gap lock is missing in the RR scenario in the RC scenario.

Perform an update other record operation:

update user set age = age +1 where id = 2;

If the execution is successful, it means that the update operation has no effect.

Verify whether there is an exclusive gap lock, and then test an insert operation:

insert into user values(null,'0010','user05',23,'重庆');

execution succeed.

Perform another insert operation:

insert into user values(null,'0007','user01',24,'武汉');

Note that the record inserted here user_nameis the same as the lock query condition, and the execution is successful, indicating that there is really no X, GAP (exclusive gap lock).

Conclusion : When the query condition is an ordinary index, select for updateit is a row-level lock.

Scenario 4.4: V8.x-RC-No Index

Perform pessimistic locking operations:

select * from user where address = '北京' for update;

Perform an update operation:

update user set age = age +1 where id = 1;

Here, the update operation is blocked, indicating that the data lock is successful.

Query lock information:

lock information

Compare the RR scenario:

lock information

For the RR scenario, there is only one exclusive row lock (X, REC_NOT_GAP) in the RC scenario.

Perform an update other record operation:

update user set age = age +1 where id = 2;

execution succeed.

Conclusion : When the query condition has no index, select for updateit is a row-level lock. The reason here is consistent with Scenario 3.4.

Scenario 4.5: V8.x-RC-index-range query

Perform pessimistic locking operations:

select * from user where id > 1 for update;

Perform an update operation:

update user set age = age +1 where id = 1;

If the execution is successful, it means that the record with id 1 is not locked.

Perform an insert operation:

insert into user values(null,'0012','user12',24,'--');

execution succeed.

The query lock information is as follows:

lock information

Compare the lock information in the RR scenario:

lock information

At this time, in the RC scenario, without the key lock, the exclusive lock becomes a row-level exclusive lock.

Conclusion : When the query condition has an index and the query condition is a range, select for updatethe data in the specified range will be locked, and only the records that meet the condition will be blocked, and the insert operation will not be affected.

Scenario and Conclusion

After completing the above experiment, we summarize all the scenarios and conclusions through a table.

Version primary key unique index normal index no index range query
MySQL 5.7.x - RR X: row lock X, row lock X, GAP: row lock, gap lock, will block within the condition range table lock The specified range is locked, and insert is blocked
MySQL 8.0.x - RR X, REC_NOT_GAP: row-level exclusive lock X, REC_NOT_GAP: row-level exclusive lock X; X, REC_NOT_GAP; X, GAP: row lock + exclusive gap lock, insert within the blocking range; Table lock, one X lock per record The specified range is locked, and insert is blocked
MySQL 5.7.x - RC X: row lock X, row lock X, row lock, no gap lock; row lock Lock the specified range, update and insert block
MySQL 8.0.x - RC X, REC_NOT_GAP: row-level exclusive lock X, REC_NOT_GAP: row-level exclusive lock X, REC_NOT_GAP: row lock, no gap lock; X, REC_NOT_GAP: row lock Lock the specified range without blocking insert

From the above table, we can conclude the following conclusions (based on RR and RC transaction isolation levels):

  • No matter which version of MySQL, if the query condition is the primary key, unique index, or ordinary index, it is a row lock;
  • When the query condition is a normal index and the transaction isolation level is RR, MySQL will also add a gap lock, and the insert and update within the condition will be blocked;
  • When the transaction isolation level is RR, the query condition has no index and is a table lock;
  • When the transaction isolation level is RC, the query condition has no index and is a row lock;
  • When the query condition is a range, if there is an index, the insert operation will not be blocked in the MySQL 8.0.x RC scenario, and the update and insert operations of the specified range will be blocked in other scenarios;

From the above conclusions, we can see that it is not simply saying "the index is the row lock, and the no index is the table lock", because when the transaction isolation level is RC, no index also behaves (optimized) as a row lock .

As for the query based on the range conditions (greater than, less than, not equal to, between, like, etc.), and the query has no results, you can verify it yourself according to the above experimental method.

This article provides experimental methods for everyone, and gives conclusions for common scenarios. I hope it can help you, and I hope everyone can like, repost, and bookmark them for emergencies.

Guess you like

Origin blog.csdn.net/wo541075754/article/details/128700501