1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-18 17:42:25 +03:00

When FOR UPDATE/SHARE is used with LIMIT, put the LockRows plan node

underneath the Limit node, not atop it.  This fixes the old problem that such
a query might unexpectedly return fewer rows than the LIMIT says, due to
LockRows discarding updated rows.

There is a related problem that LockRows might destroy the sort ordering
produced by earlier steps; but fixing that by pushing LockRows below Sort
would create serious performance problems that are unjustified in many
real-world applications, as well as potential deadlock problems from locking
many more rows than expected.  Instead, keep the present semantics of applying
FOR UPDATE after ORDER BY within a single query level; but allow the user to
specify the other way by writing FOR UPDATE in a sub-select.  To make that
work, track whether FOR UPDATE appeared explicitly in sub-selects or got
pushed down from the parent, and don't flatten a sub-select that contained an
explicit FOR UPDATE.
This commit is contained in:
Tom Lane
2009-10-28 14:55:47 +00:00
parent 44956c52c5
commit 46e3a16b05
13 changed files with 225 additions and 126 deletions

View File

@ -17,7 +17,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.394 2009/10/27 17:11:18 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.395 2009/10/28 14:55:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -60,8 +60,8 @@ static Query *transformDeclareCursorStmt(ParseState *pstate,
DeclareCursorStmt *stmt);
static Query *transformExplainStmt(ParseState *pstate,
ExplainStmt *stmt);
static void transformLockingClause(ParseState *pstate,
Query *qry, LockingClause *lc);
static void transformLockingClause(ParseState *pstate, Query *qry,
LockingClause *lc, bool pushedDown);
static bool check_parameter_resolution_walker(Node *node, ParseState *pstate);
@ -896,7 +896,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
foreach(l, stmt->lockingClause)
{
transformLockingClause(pstate, qry, (LockingClause *) lfirst(l));
transformLockingClause(pstate, qry,
(LockingClause *) lfirst(l), false);
}
return qry;
@ -1348,7 +1349,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
foreach(l, lockingClause)
{
transformLockingClause(pstate, qry, (LockingClause *) lfirst(l));
transformLockingClause(pstate, qry,
(LockingClause *) lfirst(l), false);
}
return qry;
@ -2056,7 +2058,8 @@ CheckSelectLocking(Query *qry)
* in rewriteHandler.c, and isLockedRefname() in parse_relation.c.
*/
static void
transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc)
transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
bool pushedDown)
{
List *lockedRels = lc->lockedRels;
ListCell *l;
@ -2084,16 +2087,22 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc)
switch (rte->rtekind)
{
case RTE_RELATION:
applyLockingClause(qry, i, lc->forUpdate, lc->noWait);
applyLockingClause(qry, i,
lc->forUpdate, lc->noWait, pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
applyLockingClause(qry, i,
lc->forUpdate, lc->noWait, pushedDown);
/*
* FOR UPDATE/SHARE of subquery is propagated to all of
* subquery's rels
* subquery's rels, too. We could do this later (based
* on the marking of the subquery RTE) but it is convenient
* to have local knowledge in each query level about
* which rels need to be opened with RowShareLock.
*/
transformLockingClause(pstate, rte->subquery, allrels);
transformLockingClause(pstate, rte->subquery,
allrels, true);
break;
default:
/* ignore JOIN, SPECIAL, FUNCTION, VALUES, CTE RTEs */
@ -2127,16 +2136,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc)
{
case RTE_RELATION:
applyLockingClause(qry, i,
lc->forUpdate, lc->noWait);
lc->forUpdate, lc->noWait,
pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
/*
* FOR UPDATE/SHARE of subquery is propagated to
* all of subquery's rels
*/
transformLockingClause(pstate, rte->subquery, allrels);
applyLockingClause(qry, i,
lc->forUpdate, lc->noWait,
pushedDown);
/* see comment above */
transformLockingClause(pstate, rte->subquery,
allrels, true);
break;
case RTE_JOIN:
ereport(ERROR,
@ -2190,10 +2200,15 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc)
* Record locking info for a single rangetable item
*/
void
applyLockingClause(Query *qry, Index rtindex, bool forUpdate, bool noWait)
applyLockingClause(Query *qry, Index rtindex,
bool forUpdate, bool noWait, bool pushedDown)
{
RowMarkClause *rc;
/* If it's an explicit clause, make sure hasForUpdate gets set */
if (!pushedDown)
qry->hasForUpdate = true;
/* Check for pre-existing entry for same rtindex */
if ((rc = get_parse_rowmark(qry, rtindex)) != NULL)
{
@ -2207,9 +2222,12 @@ applyLockingClause(Query *qry, Index rtindex, bool forUpdate, bool noWait)
* is a bit more debatable but raising an error doesn't seem helpful.
* (Consider for instance SELECT FOR UPDATE NOWAIT from a view that
* internally contains a plain FOR UPDATE spec.)
*
* And of course pushedDown becomes false if any clause is explicit.
*/
rc->forUpdate |= forUpdate;
rc->noWait |= noWait;
rc->pushedDown &= pushedDown;
return;
}
@ -2218,6 +2236,7 @@ applyLockingClause(Query *qry, Index rtindex, bool forUpdate, bool noWait)
rc->rti = rtindex;
rc->forUpdate = forUpdate;
rc->noWait = noWait;
rc->pushedDown = pushedDown;
qry->rowMarks = lappend(qry->rowMarks, rc);
}