@@ -140,6 +140,9 @@ WITH sleep AS (SELECT pg_sleep(1)) DELETE FROM parent_150282@parent_150282_i_idx
140
140
user testuser
141
141
142
142
statement ok
143
+ SET enable_implicit_fk_locking_for_serializable = on;
144
+ SET enable_shared_locking_for_serializable = on;
145
+ SET enable_durable_locking_for_serializable = on;
143
146
INSERT INTO child_150282 VALUES (4, 1);
144
147
145
148
user root
@@ -176,6 +179,9 @@ WITH sleep AS (SELECT pg_sleep(1)) UPDATE parent_150282 SET p = 4 WHERE i = 2;
176
179
user testuser
177
180
178
181
statement ok
182
+ SET enable_implicit_fk_locking_for_serializable = on;
183
+ SET enable_shared_locking_for_serializable = on;
184
+ SET enable_durable_locking_for_serializable = on;
179
185
INSERT INTO child_150282 VALUES (4, 1);
180
186
181
187
user root
@@ -196,3 +202,191 @@ SELECT * FROM child_150282;
196
202
4 4
197
203
198
204
subtest end
205
+
206
+ subtest fk_cascade_race_151663
207
+
208
+ statement ok
209
+ CREATE TABLE parent_151663 (
210
+ p INT PRIMARY KEY,
211
+ q INT
212
+ );
213
+
214
+ statement ok
215
+ CREATE TABLE child_151663 (
216
+ c INT PRIMARY KEY,
217
+ p INT REFERENCES parent_151663 (p),
218
+ INDEX (p),
219
+ FAMILY (c, p)
220
+ );
221
+
222
+ statement ok
223
+ CREATE USER testuser2;
224
+ CREATE USER testuser3
225
+
226
+ statement ok
227
+ GRANT ALL ON TABLE parent_151663 TO testuser;
228
+ GRANT ALL ON TABLE parent_151663 TO testuser2;
229
+ GRANT ALL ON TABLE parent_151663 TO testuser3;
230
+ GRANT ALL ON TABLE child_151663 TO testuser3
231
+
232
+ statement ok
233
+ INSERT INTO parent_151663 VALUES (1, 0), (2, 0), (3, 0)
234
+
235
+ # The timeline of this test is:
236
+ #
237
+ # testuser testuser2 testuser3 root
238
+ # -------- --------- --------- ----
239
+ # begin
240
+ # update p=2
241
+ # begin
242
+ # lock p=3
243
+ # begin RC
244
+ # start DELETE
245
+ # wait on p=3
246
+ # begin SSI
247
+ # start INSERT
248
+ # wait on p=2
249
+ # rollback
250
+ # lock p=1, p=2
251
+ # rollback
252
+ # wait on p=1
253
+ # insert c=10, c=20
254
+ # finish INSERT
255
+ # commit
256
+ # delete p=1
257
+ # FK check child
258
+ # finish DELETE
259
+ # commit
260
+ #
261
+ # The transactions run by testuser3 and root are the important part. testuser
262
+ # and testuser2 only exist to control the timing of the transactions run by
263
+ # testuser3 and root.
264
+ #
265
+ # The transactions run by testuser3 and root conflict with each other. To
266
+ # prevent a FK violation they need to either run serially, or one transaction
267
+ # needs to fail.
268
+ #
269
+ # This test demonstrates that we need locking during both the parent FK check
270
+ # and the child FK check to prevent a FK violation.
271
+ #
272
+ # The parent FK check is performed by the Serializable INSERT run by
273
+ # root. Without locking during this check, the two transactions are not
274
+ # guaranteed to detect the conflict with each other. For example, without this
275
+ # locking, the Read Committed DELETE could finish after the INSERT's FK check
276
+ # but before its insert. The INSERT could then write a phantom child row and
277
+ # have a successful read refresh, and both transactions could commit. Locking
278
+ # during the parent FK check avoids this by forcing the transactions to
279
+ # coordinate on the parent row.
280
+ #
281
+ # The child FK check is performed by the Read Committed DELETE run by
282
+ # testuser3. Without locking during this check, the Read Committed DELETE could
283
+ # perform a stale read for its child FK check, missing a newer child row. For
284
+ # example, it could miss the row written by the Serializable INSERT and
285
+ # successfully commit. (Serializable isolation does not have this same risk of
286
+ # stale reads and thus does not need locking during child FK checks.)
287
+ #
288
+ # For more discussion of this and other scenarios please see #151663.
289
+
290
+ user testuser
291
+
292
+ statement ok
293
+ BEGIN
294
+
295
+ # Use an update on p=2 to block the future insert by root.
296
+ statement ok
297
+ UPDATE parent_151663 SET q = q + 1 WHERE p = 2
298
+
299
+ user testuser2
300
+
301
+ statement ok
302
+ BEGIN
303
+
304
+ # Use a lock on p=3 to block the future delete by testuser3.
305
+ statement ok
306
+ SELECT * FROM parent_151663 WHERE p = 3 FOR UPDATE
307
+
308
+ user testuser3
309
+
310
+ statement ok
311
+ BEGIN ISOLATION LEVEL READ COMMITTED
312
+
313
+ statement ok
314
+ SELECT 1
315
+
316
+ statement async fkdelete error pgcode 23503 delete on table "parent_151663" violates foreign key constraint "child_151663_p_fkey" on table "child_151663"\nDETAIL: Key \(p\)=\(1\) is still referenced from table "child_151663"\.
317
+ DELETE FROM parent_151663 WHERE p = (SELECT 1 FROM parent_151663 WHERE p = 3 FOR UPDATE)
318
+
319
+ user root
320
+
321
+ # Give the delete a moment to wait on the p=3 lock by testuser2.
322
+ statement ok
323
+ SELECT pg_sleep(1)
324
+
325
+ # The serializable insert needs this locking to properly sychronize with the
326
+ # read committed delete.
327
+ statement ok
328
+ SET enable_implicit_fk_locking_for_serializable = on;
329
+ SET enable_shared_locking_for_serializable = on;
330
+ SET enable_durable_locking_for_serializable = on
331
+
332
+ statement ok
333
+ BEGIN ISOLATION LEVEL SERIALIZABLE
334
+
335
+ statement async fkinsert
336
+ INSERT INTO child_151663 VALUES (10, 1), (20, 2)
337
+
338
+ user testuser
339
+
340
+ # Give the insert a moment to wait on the p=2 update by testuser.
341
+ statement ok
342
+ SELECT pg_sleep(1)
343
+
344
+ statement ok
345
+ ROLLBACK
346
+
347
+ user testuser2
348
+
349
+ # Give the insert a moment to lock p=1.
350
+ statement ok
351
+ SELECT pg_sleep(1)
352
+
353
+ statement ok
354
+ ROLLBACK
355
+
356
+ user root
357
+
358
+ awaitstatement fkinsert
359
+
360
+ statement ok
361
+ COMMIT
362
+
363
+ user testuser3
364
+
365
+ awaitstatement fkdelete
366
+
367
+ statement ok
368
+ COMMIT
369
+
370
+ user root
371
+
372
+ # Check that there are no orphan children.
373
+
374
+ query II
375
+ SELECT * FROM parent_151663 ORDER BY p
376
+ ----
377
+ 1 0
378
+ 2 0
379
+ 3 0
380
+
381
+ query II
382
+ SELECT * FROM child_151663 ORDER BY c
383
+ ----
384
+ 10 1
385
+ 20 2
386
+
387
+ statement ok
388
+ RESET enable_implicit_fk_locking_for_serializable;
389
+ RESET enable_shared_locking_for_serializable;
390
+ RESET enable_durable_locking_for_serializable
391
+
392
+ subtest end
0 commit comments