Doc: show how to get the equivalent of LIMIT for UPDATE/DELETE.
authorTom Lane
Sun, 7 Apr 2024 20:26:47 +0000 (16:26 -0400)
committerTom Lane
Sun, 7 Apr 2024 20:26:47 +0000 (16:26 -0400)
Add examples showing use of a CTE and a self-join to perform
partial UPDATEs and DELETEs.

Corey Huinker, reviewed by Laurenz Albe

Discussion: https://postgr.es/m/CADkLM=caNEQsUwPWnfi2jR4ix99E0EJM_3jtcE-YjnEQC7Rssw@mail.gmail.com

doc/src/sgml/ref/delete.sgml
doc/src/sgml/ref/update.sgml

index 1b81b4e7d743f49c52e17deaa6035adec878a7bf..0b6fa0051235ae6e680c0213adcd093da4505c2f 100644 (file)
@@ -260,12 +260,32 @@ DELETE FROM tasks WHERE status = 'DONE' RETURNING *;
 
   
 
-   
+  
    Delete the row of tasks on which the cursor
    c_tasks is currently positioned:
 
 DELETE FROM tasks WHERE CURRENT OF c_tasks;
-
+
+  
+
+  
+   While there is no LIMIT clause
+   for DELETE, it is possible to get a similar effect
+   using the same method described in the
+   documentation of UPDATE:
+
+WITH delete_batch AS (
+  SELECT l.ctid FROM user_logs AS l
+    WHERE l.status = 'archived'
+    ORDER BY l.creation_date
+    FOR UPDATE
+    LIMIT 10000
+)
+DELETE FROM user_logs AS dl
+  USING delete_batch AS del
+  WHERE dl.ctid = del.ctid;
+
+  
  
 
  
index 2ab24b0523ef2dde94dd427c4a1cc2d64032162c..babb34fa511d66f8c729e74a449964cd14705a43 100644 (file)
@@ -441,7 +441,45 @@ COMMIT;
    c_films is currently positioned:
 
 UPDATE films SET kind = 'Dramatic' WHERE CURRENT OF c_films;
-
+
+  
+
+  
+   Updates affecting many rows can have negative effects on system
+   performance, such as table bloat, increased replica lag, and increased
+   lock contention.  In such situations it can make sense to perform the
+   operation in smaller batches, possibly with a VACUUM
+   operation on the table between batches.  While there is
+   no LIMIT clause for UPDATE, it is
+   possible to get a similar effect through the use of
+   a Common Table Expression and a
+   self-join.  With the standard PostgreSQL
+   table access method, a self-join on the system
+   column ctid is very
+   efficient:
+
+WITH exceeded_max_retries AS (
+  SELECT w.ctid FROM work_item AS w
+    WHERE w.status = 'active' AND w.num_retries > 10
+    ORDER BY w.retry_timestamp
+    FOR UPDATE
+    LIMIT 5000
+)
+UPDATE work_item SET status = 'failed'
+  FROM exceeded_max_retries AS emr
+  WHERE work_item.ctid = emr.ctid;
+
+   This command will need to be repeated until no rows remain to be updated.
+   Use of an ORDER BY clause allows the command to
+   prioritize which rows will be updated; it can also prevent deadlock
+   with other update operations if they use the same ordering.
+   If lock contention is a concern, then SKIP LOCKED
+   can be added to the CTE to prevent multiple commands
+   from updating the same row.  However, then a
+   final UPDATE without SKIP LOCKED
+   or LIMIT will be needed to ensure that no matching
+   rows were overlooked.
+