@@ -2387,26 +2387,31 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
23872387 */
23882388 heaptup = heap_prepare_insert (relation , tup , xid , cid , options );
23892389
2390+ /*
2391+ * Find buffer to insert this tuple into. If the page is all visible,
2392+ * this will also pin the requisite visibility map page.
2393+ */
2394+ buffer = RelationGetBufferForTuple (relation , heaptup -> t_len ,
2395+ InvalidBuffer , options , bistate ,
2396+ & vmbuffer , NULL );
2397+
23902398 /*
23912399 * We're about to do the actual insert -- but check for conflict first, to
23922400 * avoid possibly having to roll back work we've just done.
23932401 *
2402+ * This is safe without a recheck as long as there is no possibility of
2403+ * another process scanning the page between this check and the insert
2404+ * being visible to the scan (i.e., an exclusive buffer content lock is
2405+ * continuously held from this point until the tuple insert is visible).
2406+ *
23942407 * For a heap insert, we only need to check for table-level SSI locks. Our
23952408 * new tuple can't possibly conflict with existing tuple locks, and heap
23962409 * page locks are only consolidated versions of tuple locks; they do not
2397- * lock "gaps" as index page locks do. So we don't need to identify a
2398- * buffer before making the call.
2410+ * lock "gaps" as index page locks do. So we don't need to specify a
2411+ * buffer when making the call, which makes for a faster check .
23992412 */
24002413 CheckForSerializableConflictIn (relation , NULL , InvalidBuffer );
24012414
2402- /*
2403- * Find buffer to insert this tuple into. If the page is all visible,
2404- * this will also pin the requisite visibility map page.
2405- */
2406- buffer = RelationGetBufferForTuple (relation , heaptup -> t_len ,
2407- InvalidBuffer , options , bistate ,
2408- & vmbuffer , NULL );
2409-
24102415 /* NO EREPORT(ERROR) from here till changes are logged */
24112416 START_CRIT_SECTION ();
24122417
@@ -2660,13 +2665,26 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
26602665
26612666 /*
26622667 * We're about to do the actual inserts -- but check for conflict first,
2663- * to avoid possibly having to roll back work we've just done.
2668+ * to minimize the possibility of having to roll back work we've just
2669+ * done.
26642670 *
2665- * For a heap insert, we only need to check for table-level SSI locks. Our
2666- * new tuple can't possibly conflict with existing tuple locks, and heap
2671+ * A check here does not definitively prevent a serialization anomaly;
2672+ * that check MUST be done at least past the point of acquiring an
2673+ * exclusive buffer content lock on every buffer that will be affected,
2674+ * and MAY be done after all inserts are reflected in the buffers and
2675+ * those locks are released; otherwise there race condition. Since
2676+ * multiple buffers can be locked and unlocked in the loop below, and it
2677+ * would not be feasible to identify and lock all of those buffers before
2678+ * the loop, we must do a final check at the end.
2679+ *
2680+ * The check here could be omitted with no loss of correctness; it is
2681+ * present strictly as an optimization.
2682+ *
2683+ * For heap inserts, we only need to check for table-level SSI locks. Our
2684+ * new tuples can't possibly conflict with existing tuple locks, and heap
26672685 * page locks are only consolidated versions of tuple locks; they do not
2668- * lock "gaps" as index page locks do. So we don't need to identify a
2669- * buffer before making the call.
2686+ * lock "gaps" as index page locks do. So we don't need to specify a
2687+ * buffer when making the call, which makes for a faster check .
26702688 */
26712689 CheckForSerializableConflictIn (relation , NULL , InvalidBuffer );
26722690
@@ -2845,6 +2863,22 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
28452863 ndone += nthispage ;
28462864 }
28472865
2866+ /*
2867+ * We're done with the actual inserts. Check for conflicts again, to
2868+ * ensure that all rw-conflicts in to these inserts are detected. Without
2869+ * this final check, a sequential scan of the heap may have locked the
2870+ * table after the "before" check, missing one opportunity to detect the
2871+ * conflict, and then scanned the table before the new tuples were there,
2872+ * missing the other chance to detect the conflict.
2873+ *
2874+ * For heap inserts, we only need to check for table-level SSI locks. Our
2875+ * new tuples can't possibly conflict with existing tuple locks, and heap
2876+ * page locks are only consolidated versions of tuple locks; they do not
2877+ * lock "gaps" as index page locks do. So we don't need to specify a
2878+ * buffer when making the call.
2879+ */
2880+ CheckForSerializableConflictIn (relation , NULL , InvalidBuffer );
2881+
28482882 /*
28492883 * If tuples are cachable, mark them for invalidation from the caches in
28502884 * case we abort. Note it is OK to do this after releasing the buffer,
@@ -3158,6 +3192,11 @@ heap_delete(Relation relation, ItemPointer tid,
31583192 /*
31593193 * We're about to do the actual delete -- check for conflict first, to
31603194 * avoid possibly having to roll back work we've just done.
3195+ *
3196+ * This is safe without a recheck as long as there is no possibility of
3197+ * another process scanning the page between this check and the delete
3198+ * being visible to the scan (i.e., an exclusive buffer content lock is
3199+ * continuously held from this point until the tuple delete is visible).
31613200 */
31623201 CheckForSerializableConflictIn (relation , & tp , buffer );
31633202
@@ -3785,12 +3824,6 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
37853824 goto l2 ;
37863825 }
37873826
3788- /*
3789- * We're about to do the actual update -- check for conflict first, to
3790- * avoid possibly having to roll back work we've just done.
3791- */
3792- CheckForSerializableConflictIn (relation , & oldtup , buffer );
3793-
37943827 /* Fill in transaction status data */
37953828
37963829 /*
@@ -3979,14 +4012,20 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
39794012 }
39804013
39814014 /*
3982- * We're about to create the new tuple -- check for conflict first, to
4015+ * We're about to do the actual update -- check for conflict first, to
39834016 * avoid possibly having to roll back work we've just done.
39844017 *
3985- * NOTE: For a tuple insert, we only need to check for table locks, since
3986- * predicate locking at the index level will cover ranges for anything
3987- * except a table scan. Therefore, only provide the relation.
4018+ * This is safe without a recheck as long as there is no possibility of
4019+ * another process scanning the pages between this check and the update
4020+ * being visible to the scan (i.e., exclusive buffer content lock(s) are
4021+ * continuously held from this point until the tuple update is visible).
4022+ *
4023+ * For the new tuple the only check needed is at the relation level, but
4024+ * since both tuples are in the same relation and the check for oldtup
4025+ * will include checking the relation level, there is no benefit to a
4026+ * separate check for the new tuple.
39884027 */
3989- CheckForSerializableConflictIn (relation , NULL , InvalidBuffer );
4028+ CheckForSerializableConflictIn (relation , & oldtup , buffer );
39904029
39914030 /*
39924031 * At this point newbuf and buffer are both pinned and locked, and newbuf
0 commit comments