Row lock in pgsql

Row lock in pgsql

Foreword

In the daily work, for the operation of the same resource, sometimes we inevitably need to add a lock to prevent it from being deleted or changed by other processes during the operation. Then more are row-level locks. under.

User visible lock

Visible from system view pg_locks

User-visible locks, which users can actively call themselves, can see whether they are granted locks in pg_locks. Including regular lock and consulting lock.

regular Lock

Regular lock is divided into table level and row level.

Row level

Through some database operations, some row locks are automatically obtained. Row locks do not block data queries, only writes and lockers, such as the following operations.

FOR UPDATE
FOR NO KEY UPFATE
FOR SHARE
FOR KEY SHARE

FOR UPDATE

FOR UPDATE locks enable SELECT statements to obtain row-level locks for updating data. Locking the row can prevent the row from being acquired by other transactions or being modified and deleted during this operation. This means that the operations of other transactions will be blocked until the end of the current transaction; similarly, the SELECT FOR UPDATE command will wait until the
end of the previous transaction. That is, other transactions that attempt UPDATE, DELETE, SELECT FOR UPDATE, SELECT FOR NO KEY UPDATE, SELECT FOR SHARE, or SELECT FOR KEY SHARE will be blocked. In turn, SELECT FOR UPDATE will wait for
concurrent transactions that have run these commands on the same row , and then lock and return the updated row (or no row because the row may have been deleted). However, in a REPEATABLE READ or SERIALIZABLE transaction, if a row to be locked is changed after the transaction starts, an error will be thrown.

Any DELETE command on a row will also get the FOR UPDATE lock mode, and UPDATE that modifies the value on some columns will also get the lock mode. The set of columns considered in the current UPDATE case are those with unique indexes that can be used for foreign keys (so partial indexes and expression indexes are not considered), but this requirement may change in the future.

FOR NO KEY UPDATE

Similar to the FOR UPDATE command, but the requirements for acquiring locks are more relaxed, and the SELECT FOR KEY SHARE command will not be blocked on the same line. Also in the UPDATE command, if the FOR UPDATE lock is not acquired, the lock will be acquired.

FOR SHARE

The behavior is similar to FOR NO KEY UPDATE, but it acquires a shared lock instead of an exclusive lock on each retrieved row. A shared lock will prevent other transactions from performing UPDATE, DELETE, SELECT FOR UPDATE, or SELECT FOR NO KEY UPDATE on these rows, but it will not prevent them from performing SELECT FOR SHARE or SELECT FOR KEY SHARE.

FOR KEY SHARE

The behavior is similar to FOR SHARE, but the lock is weak: SELECT FOR UPDATE will be blocked, but SELECT FOR NO KEY UPDATE will not be blocked. A key shared lock will block other transactions from executing DELETE or UPDATE that modify the key value, but will not block other UPDATE, nor will it prevent SELECT FOR NO KEY UPDATE, SELECT FOR SHARE or SELECT FOR KEY SHARE.

Test data visibility after locking

create table test_lock
(
    id   serial not null,
    name text   not null
);

alter table test_lock
    owner to postgres;

create unique index test_lock_id_uindex
    on test_lock (id);

INSERT INTO public.test_lock (id, name) VALUES (1, '小明');
INSERT INTO public.test_lock (id, name) VALUES (2, '小白');

Lock test (FOR UPDATE)

Query 1

/*查询事务1*/
begin;
select *
from test_lock
where id = 1
for update

Query 2

/*查询事务2*/
begin;
select *
from test_lock
where id = 1
for update

When transaction 1 locks resources in the query, transaction 2 cannot find the data and waits for transaction 1 to commit

Query 1 transaction commit

commit

The query of transaction 2 immediately ends and the current data is queried

Don't forget to commit the transaction 2

Lock test (FOR UPDATE, UPDATE)

UPDATE and DELETE, the operation is also locked, under test and FOR UPDATE blocking situation
Query 1

/*查询事务1*/
begin;
update test_lock set name='mignming' where id=1

Query 2

/*查询事务2*/
begin;
select *
from test_lock
where id = 1
for update

Found that SELECT FOR UPDATE is blocked

Submit query 1, update transaction

commit

Then query 2 ends blocking and gets the data updated by transaction 1.

Don't forget to commit the transaction 2

Command description

begin;--开启事务

begin transaction;--开启事务

commit;--提交

rollback;--回滚

set lock_timeout=5000;--设置超时时间

Points to note

When querying even tables, it does not support unilateral connection. For example:

select u.*,r.* from db_user u left join db_role r on u.roleid=r.id for update;

The following forms are supported and the data associated in the two tables is locked:

select u.*,r.* from db_user u, db_role r where u.roleid=r.id for update;

Give a chestnut

There is a category table and there is a document table. A category corresponds to multiple documents. There is a limitation when deleting a category. There must be no documents under the category to delete it. At this time, such a scenario may occur when deleting. When deleting the category, a new document is created later, and the two transactions are parallel. According to the default transaction isolation level of pgsql, the read has been committed. The newly created document, the classification information obtained by the classification id is the information before the classification is deleted, that is, the existence of this classification is queried. When the two things are executed together, it may happen that the category id of the newly created document does not exist. Then there is dirty data.

How to solve it?

The first thing that comes to mind must be locking.

If a resource is locked, the subsequent operations must wait until the previous resource operation ends to obtain resource information.

This is the query description of the pgsq document for reading the row lock in the submitted isolation. It should be noted that UPDATE and DELETE will also lock the resource.

UPDATE, DELETE, SELECT FOR UPDATE, and SELECT FOR SHARE commands behave the same as SELECT when searching for the target row: they will only find rows that have been committed at the beginning of the command. However, when it is found, such a target row may have been updated (or deleted or locked) by other concurrent transactions. In this case, the upcoming update will wait for the first update transaction to be committed or rolled back (if it is still in progress). If the first update transaction is rolled back, then its effect will be ignored and the second transaction can continue to update the originally discovered row. If the first update transaction commits, if the row is deleted by the first updater, the second update transaction will ignore the row, otherwise the second updater will try to apply it on the updated version of the row Operation. The search condition (WHERE clause) of the command will be recalculated to see if the updated version of the row still meets the search condition. If it matches, the second updater uses the updated version of the row to continue its operation. In the case of SELECT FOR UPDATE and SELECT FOR SHARE, this means locking the updated version of the row and returning it to the client.

Therefore, the solution is to lock the classification id when adding the document, so that the deletion of the classification lock is mutually exclusive with the lock queried below. The two must have an order of execution to avoid the generation of dirty data.

	WITH lock_document_categories_cte AS (
		SELECT id
		FROM document_categories
		WHERE id = ${categoryId}
		AND enterprise_id = ${enterpriseId}
		FOR UPDATE
	),lock_document_directories_cte AS (
		SELECT id
		FROM document_directories
		WHERE id = ${directoryId}
		AND enterprise_id = ${enterpriseId}
		FOR UPDATE
	)
	INSERT INTO documents (
		enterprise_id, directory_id, category_id, code, name, author_id
	) VALUES (
		${enterpriseId}, (SELECT * FROM lock_document_directories_cte), (SELECT * FROM lock_document_categories_cte), ${code}, ${name}, ${authorId}
	)
	RETURNING id

Through the FOR UPDATE in lock_document_categories_cte, the resource is locked so that it is mutually exclusive with the lock in delete.

to sum up

UPDATE, DELETE, SELECT FOR UPDATE, and SELECT FOR SHARE. Will lock the resource. When the locked resource is being executed. The latter operation will not be completed until the previous resource operation is completed. The upcoming update will wait for the first update transaction to be committed or rolled back (if it is still in progress). If the first update transaction is rolled back, then its effect will be ignored and the second transaction can continue to update the originally discovered row. If the first update transaction commits, if the row is deleted by the first updater, the second update transaction will ignore the row, otherwise the second updater will try to apply it on the updated version of the row Operation. The search condition (WHERE clause) of the command will be recalculated to see if the updated version of the row still meets the search condition. If it matches, the second updater uses the updated version of the row to continue its operation. In the case of SELECT FOR UPDATE and SELECT FOR SHARE, this means locking the updated version of the row and returning it to the client.

reference

[Postgresql lock mechanism (table lock and row lock)] https://blog.csdn.net/turbo_zone/article/details/84036511
[postgresql row-level lock for update test] https://blog.csdn.net/shuoyu816/ article / details / 80086810
[PostgreSQL lock decryption] https://www.oschina.net/translate/postgresql-locking-revealed
[explicit locking] http://postgres.cn/docs/11/explicit-locking.html

Guess you like

Origin www.cnblogs.com/ricklz/p/12671100.html