Tom Lane <[email protected]> 于2026年10月25日周日 08:56写道:
>
> So I still don't see a reachable bug here, but there's dead code
> (the bit about handling an rmark without kmark), and the
> commentary omits even more than I thought before.
>

Thanks for the very detailed analysis. I tested the cases you
described, and you are right: my original example does not demonstrate
a reachable correctness bug.

Following your suggestion, the attached v2 patch removes the dead code
for transferring an rmark when there is no kmark, adds assertions for
that invariant, and expands the comments about why relations with
different row mark types are not currently merged.


> I experimented with writing logic that would allow merging locks of
> different strengths, but I'm not really convinced that it's worth the
> trouble.

I haven't included the other improvement you suggested—performing SJE
when lock strengths differ and retaining the stronger lock type, along
with an appropriate rule for choosing the waitPolicy.
I think it would be better to consider that separately from this cleanup patch.

During testing, I also noticed another possible missed optimization.
For example, adding a restriction such as:
postgres=# EXPLAIN (VERBOSE, COSTS OFF)
SELECT p.a, q.a
FROM t1 p
JOIN t1 q ON p.a = q.a
WHERE p.a = 18;
          QUERY PLAN
-------------------------------
 Nested Loop
   Output: p.a, q.a
   ->  Seq Scan on public.t1 p
         Output: p.a, p.b
         Filter: (p.a = 18)
   ->  Seq Scan on public.t1 q
         Output: q.a, q.b
         Filter: (q.a = 18)
(8 rows)

to the self-join query prevents SJE, whereas removing that restriction
allows SJE to occur.
It appears that the equivalence class containing the constant absorbs
the equality between p.a and q.a, so
generate_join_implied_equalities() returns NIL and SJE gives up.

Do you think this case is worth improving, perhaps as a separate patch?

-- 
Thanks,
Tender Wang
From 46212f3111b0290ded005ab3c6e3bc5d914742c8 Mon Sep 17 00:00:00 2001
From: Tender Wang <[email protected]>
Date: Mon, 26 Oct 2026 10:59:30 +0800
Subject: [PATCH v2] Clarify row mark handling in self-join elimination

When row marks are needed, preprocess_rowmarks() creates one for every
non-target base relation.  Since target relations are not candidates for
self-join elimination, a pair of candidate relations must either both
have row marks or both lack them.

Therefore, the code that transfers the removed relation's row mark when
the kept relation has none is unreachable.  Remove that code, add
assertions for the invariant, and expand the comments explaining why
relations with different row mark types are not merged.
---
 src/backend/optimizer/plan/analyzejoins.c | 40 +++++++++--------------
 1 file changed, 16 insertions(+), 24 deletions(-)

diff --git a/src/backend/optimizer/plan/analyzejoins.c 
b/src/backend/optimizer/plan/analyzejoins.c
index 7aad91fd34e..dcfe5097586 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -1287,28 +1287,13 @@ remove_self_join_rel(PlannerInfo *root,
        Assert(found_relid);
 
        /*
-        * If the removed relation has a row mark, transfer it to the remaining
-        * one.
-        *
-        * If both rels have row marks, just keep the one corresponding to the
-        * remaining relation because we verified earlier that they have the 
same
-        * strength.
+        * If the relations have row marks, discard the removed relation's mark.
+        * The caller has verified that both marks exist and have the same type.
         */
        if (rmark)
        {
-               if (kmark)
-               {
-                       Assert(kmark->markType == rmark->markType);
-
-                       root->rowMarks = list_delete_ptr(root->rowMarks, rmark);
-               }
-               else
-               {
-                       /* Shouldn't have inheritance children yet. */
-                       Assert(rmark->rti == rmark->prti);
-
-                       rmark->rti = rmark->prti = toKeep->relid;
-               }
+               Assert(rmark->markType == kmark->markType);
+               root->rowMarks = list_delete_ptr(root->rowMarks, rmark);
        }
 }
 
@@ -1827,10 +1812,16 @@ remove_self_joins_one_group(PlannerInfo *root, Relids 
relids)
                                continue;
 
                        /*
-                        * Check Row Marks equivalence. We can't remove the 
join if the
-                        * relations have row marks of different strength 
(e.g., one is
-                        * locked FOR UPDATE, and another just has 
ROW_MARK_REFERENCE for
-                        * EvalPlanQual rechecking).
+                        * If row marks are needed, preprocess_rowmarks() 
creates one for
+                        * every non-target base relation.  Target relations 
aren't SJE
+                        * candidates, so these relations must either both have 
row marks or
+                        * both lack them.
+                        *
+                        * Don't merge relations with different row mark types. 
 In
+                        * particular, a locking row mark can fetch an updated 
version of a
+                        * tuple, while a non-locking row mark preserves the 
version found by
+                        * the original scan.  We also conservatively reject 
different locking
+                        * row mark types rather than try to combine their lock 
strengths.
                         */
                        foreach(lc, root->rowMarks)
                        {
@@ -1850,7 +1841,8 @@ remove_self_joins_one_group(PlannerInfo *root, Relids 
relids)
                                if (kmark && rmark)
                                        break;
                        }
-                       if (kmark && rmark && kmark->markType != 
rmark->markType)
+                       Assert((kmark == NULL) == (rmark == NULL));
+                       if (kmark && kmark->markType != rmark->markType)
                                continue;
 
                        /*
-- 
2.43.0

Reply via email to