1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-09 06:21:09 +03:00

Implement sharable row-level locks, and use them for foreign key references

to eliminate unnecessary deadlocks.  This commit adds SELECT ... FOR SHARE
paralleling SELECT ... FOR UPDATE.  The implementation uses a new SLRU
data structure (managed much like pg_subtrans) to represent multiple-
transaction-ID sets.  When more than one transaction is holding a shared
lock on a particular row, we create a MultiXactId representing that set
of transactions and store its ID in the row's XMAX.  This scheme allows
an effectively unlimited number of row locks, just as we did before,
while not costing any extra overhead except when a shared lock actually
has to be shared.   Still TODO: use the regular lock manager to control
the grant order when multiple backends are waiting for a row lock.

Alvaro Herrera and Tom Lane.
This commit is contained in:
Tom Lane
2005-04-28 21:47:18 +00:00
parent d902e7d63b
commit bedb78d386
55 changed files with 2802 additions and 439 deletions

View File

@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.320 2005/04/14 20:03:24 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.321 2005/04/28 21:47:14 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -134,7 +134,7 @@ static void transformFKConstraints(ParseState *pstate,
bool isAddConstraint);
static void applyColumnNames(List *dst, List *src);
static List *getSetColTypes(ParseState *pstate, Node *node);
static void transformForUpdate(Query *qry, List *forUpdate);
static void transformLocking(Query *qry, List *lockedRels, bool forUpdate);
static void transformConstraintAttrs(List *constraintList);
static void transformColumnType(ParseState *pstate, ColumnDef *column);
static void release_pstate_resources(ParseState *pstate);
@@ -1810,8 +1810,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->commandType = CMD_SELECT;
/* make FOR UPDATE clause available to addRangeTableEntry */
pstate->p_forUpdate = stmt->forUpdate;
/* make FOR UPDATE/FOR SHARE list available to addRangeTableEntry */
pstate->p_lockedRels = stmt->lockedRels;
/* process the FROM clause */
transformFromClause(pstate, stmt->fromClause);
@@ -1870,8 +1870,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
parseCheckAggregates(pstate, qry);
if (stmt->forUpdate != NIL)
transformForUpdate(qry, stmt->forUpdate);
if (stmt->lockedRels != NIL)
transformLocking(qry, stmt->lockedRels, stmt->forUpdate);
return qry;
}
@@ -1899,7 +1899,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
List *sortClause;
Node *limitOffset;
Node *limitCount;
List *forUpdate;
List *lockedRels;
bool forUpdate;
Node *node;
ListCell *left_tlist,
*dtlist;
@@ -1937,18 +1938,19 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
sortClause = stmt->sortClause;
limitOffset = stmt->limitOffset;
limitCount = stmt->limitCount;
lockedRels = stmt->lockedRels;
forUpdate = stmt->forUpdate;
stmt->sortClause = NIL;
stmt->limitOffset = NULL;
stmt->limitCount = NULL;
stmt->forUpdate = NIL;
stmt->lockedRels = NIL;
/* We don't support forUpdate with set ops at the moment. */
if (forUpdate)
/* We don't support FOR UPDATE/SHARE with set ops at the moment. */
if (lockedRels)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT")));
errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
/*
* Recursively transform the components of the tree.
@@ -2083,8 +2085,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
parseCheckAggregates(pstate, qry);
if (forUpdate != NIL)
transformForUpdate(qry, forUpdate);
if (lockedRels != NIL)
transformLocking(qry, lockedRels, forUpdate);
return qry;
}
@@ -2107,11 +2109,11 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT")));
/* We don't support forUpdate with set ops at the moment. */
if (stmt->forUpdate)
/* We don't support FOR UPDATE/SHARE with set ops at the moment. */
if (stmt->lockedRels)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT")));
errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
/*
* If an internal node of a set-op tree has ORDER BY, UPDATE, or LIMIT
@@ -2128,7 +2130,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
{
Assert(stmt->larg != NULL && stmt->rarg != NULL);
if (stmt->sortClause || stmt->limitOffset || stmt->limitCount ||
stmt->forUpdate)
stmt->lockedRels)
isLeaf = true;
else
isLeaf = false;
@@ -2711,47 +2713,67 @@ transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt)
/* exported so planner can check again after rewriting, query pullup, etc */
void
CheckSelectForUpdate(Query *qry)
CheckSelectLocking(Query *qry, bool forUpdate)
{
const char *operation;
if (forUpdate)
operation = "SELECT FOR UPDATE";
else
operation = "SELECT FOR SHARE";
if (qry->setOperations)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT")));
/* translator: %s is a SQL command, like SELECT FOR UPDATE */
errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT", operation)));
if (qry->distinctClause != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE is not allowed with DISTINCT clause")));
/* translator: %s is a SQL command, like SELECT FOR UPDATE */
errmsg("%s is not allowed with DISTINCT clause", operation)));
if (qry->groupClause != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE is not allowed with GROUP BY clause")));
/* translator: %s is a SQL command, like SELECT FOR UPDATE */
errmsg("%s is not allowed with GROUP BY clause", operation)));
if (qry->havingQual != NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE is not allowed with HAVING clause")));
/* translator: %s is a SQL command, like SELECT FOR UPDATE */
errmsg("%s is not allowed with HAVING clause", operation)));
if (qry->hasAggs)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE is not allowed with aggregate functions")));
/* translator: %s is a SQL command, like SELECT FOR UPDATE */
errmsg("%s is not allowed with aggregate functions", operation)));
}
/*
* Convert FOR UPDATE name list into rowMarks list of integer relids
* Convert FOR UPDATE/SHARE name list into rowMarks list of integer relids
*
* NB: if you need to change this, see also markQueryForUpdate()
* NB: if you need to change this, see also markQueryForLocking()
* in rewriteHandler.c.
*/
static void
transformForUpdate(Query *qry, List *forUpdate)
transformLocking(Query *qry, List *lockedRels, bool forUpdate)
{
List *rowMarks = qry->rowMarks;
List *rowMarks;
ListCell *l;
ListCell *rt;
Index i;
CheckSelectForUpdate(qry);
if (qry->rowMarks && forUpdate != qry->forUpdate)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot use both FOR UPDATE and FOR SHARE in one query")));
qry->forUpdate = forUpdate;
if (linitial(forUpdate) == NULL)
CheckSelectLocking(qry, forUpdate);
rowMarks = qry->rowMarks;
if (linitial(lockedRels) == NULL)
{
/* all regular tables used in query */
i = 0;
@@ -2770,10 +2792,11 @@ transformForUpdate(Query *qry, List *forUpdate)
case RTE_SUBQUERY:
/*
* FOR UPDATE of subquery is propagated to subquery's
* rels
* FOR UPDATE/SHARE of subquery is propagated to all
* of subquery's rels
*/
transformForUpdate(rte->subquery, list_make1(NULL));
transformLocking(rte->subquery, list_make1(NULL),
forUpdate);
break;
default:
/* ignore JOIN, SPECIAL, FUNCTION RTEs */
@@ -2784,7 +2807,7 @@ transformForUpdate(Query *qry, List *forUpdate)
else
{
/* just the named tables */
foreach(l, forUpdate)
foreach(l, lockedRels)
{
char *relname = strVal(lfirst(l));
@@ -2806,25 +2829,26 @@ transformForUpdate(Query *qry, List *forUpdate)
case RTE_SUBQUERY:
/*
* FOR UPDATE of subquery is propagated to
* subquery's rels
* FOR UPDATE/SHARE of subquery is propagated to
* all of subquery's rels
*/
transformForUpdate(rte->subquery, list_make1(NULL));
transformLocking(rte->subquery, list_make1(NULL),
forUpdate);
break;
case RTE_JOIN:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE cannot be applied to a join")));
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a join")));
break;
case RTE_SPECIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE cannot be applied to NEW or OLD")));
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to NEW or OLD")));
break;
case RTE_FUNCTION:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE cannot be applied to a function")));
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a function")));
break;
default:
elog(ERROR, "unrecognized RTE type: %d",
@@ -2837,7 +2861,7 @@ transformForUpdate(Query *qry, List *forUpdate)
if (rt == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" in FOR UPDATE clause not found in FROM clause",
errmsg("relation \"%s\" in FOR UPDATE/SHARE clause not found in FROM clause",
relname)));
}
}

View File

@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.488 2005/04/23 17:22:16 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.489 2005/04/28 21:47:14 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -87,7 +87,7 @@ static List *check_func_name(List *names);
static List *extractArgTypes(List *parameters);
static SelectStmt *findLeftmostSelect(SelectStmt *node);
static void insertSelectOptions(SelectStmt *stmt,
List *sortClause, List *forUpdate,
List *sortClause, List *lockingClause,
Node *limitOffset, Node *limitCount);
static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
static Node *doNegate(Node *n);
@@ -242,7 +242,8 @@ static void doNegateFloat(Value *v);
%type <oncommit> OnCommitOption
%type <withoids> OptWithOids WithOidsAs
%type <list> for_update_clause opt_for_update_clause update_list
%type <list> for_locking_clause opt_for_locking_clause
update_list
%type <boolean> opt_all
%type <node> join_outer join_qual
@@ -4886,9 +4887,9 @@ select_with_parens:
;
/*
* FOR UPDATE may be before or after LIMIT/OFFSET.
* FOR UPDATE/SHARE may be before or after LIMIT/OFFSET.
* In <=7.2.X, LIMIT/OFFSET had to be after FOR UPDATE
* We now support both orderings, but prefer LIMIT/OFFSET before FOR UPDATE
* We now support both orderings, but prefer LIMIT/OFFSET before FOR UPDATE/SHARE
* 2002-08-28 bjm
*/
select_no_parens:
@@ -4899,13 +4900,13 @@ select_no_parens:
NULL, NULL);
$$ = $1;
}
| select_clause opt_sort_clause for_update_clause opt_select_limit
| select_clause opt_sort_clause for_locking_clause opt_select_limit
{
insertSelectOptions((SelectStmt *) $1, $2, $3,
list_nth($4, 0), list_nth($4, 1));
$$ = $1;
}
| select_clause opt_sort_clause select_limit opt_for_update_clause
| select_clause opt_sort_clause select_limit opt_for_locking_clause
{
insertSelectOptions((SelectStmt *) $1, $2, $4,
list_nth($3, 0), list_nth($3, 1));
@@ -5146,13 +5147,14 @@ having_clause:
| /*EMPTY*/ { $$ = NULL; }
;
for_update_clause:
FOR UPDATE update_list { $$ = $3; }
for_locking_clause:
FOR UPDATE update_list { $$ = lcons(makeString("for_update"), $3); }
| FOR SHARE update_list { $$ = lcons(makeString("for_share"), $3); }
| FOR READ ONLY { $$ = NULL; }
;
opt_for_update_clause:
for_update_clause { $$ = $1; }
opt_for_locking_clause:
for_locking_clause { $$ = $1; }
| /* EMPTY */ { $$ = NULL; }
;
@@ -8379,7 +8381,7 @@ findLeftmostSelect(SelectStmt *node)
*/
static void
insertSelectOptions(SelectStmt *stmt,
List *sortClause, List *forUpdate,
List *sortClause, List *lockingClause,
Node *limitOffset, Node *limitCount)
{
/*
@@ -8394,13 +8396,27 @@ insertSelectOptions(SelectStmt *stmt,
errmsg("multiple ORDER BY clauses not allowed")));
stmt->sortClause = sortClause;
}
if (forUpdate)
if (lockingClause)
{
if (stmt->forUpdate)
Value *type;
if (stmt->lockedRels)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple FOR UPDATE clauses not allowed")));
stmt->forUpdate = forUpdate;
errmsg("multiple FOR UPDATE/FOR SHARE clauses not allowed")));
Assert(list_length(lockingClause) > 1);
/* 1st is Value node containing "for_update" or "for_share" */
type = (Value *) linitial(lockingClause);
Assert(IsA(type, String));
if (strcmp(strVal(type), "for_update") == 0)
stmt->forUpdate = true;
else if (strcmp(strVal(type), "for_share") == 0)
stmt->forUpdate = false;
else
elog(ERROR, "invalid first node in locking clause");
stmt->lockedRels = list_delete_first(lockingClause);
}
if (limitOffset)
{

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.106 2005/04/13 16:50:55 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.107 2005/04/28 21:47:14 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -40,7 +40,7 @@ static Node *scanNameSpaceForRelid(ParseState *pstate, Node *nsnode,
Oid relid);
static void scanNameSpaceForConflict(ParseState *pstate, Node *nsnode,
RangeTblEntry *rte1, const char *aliasname1);
static bool isForUpdate(ParseState *pstate, char *refname);
static bool isLockedRel(ParseState *pstate, char *refname);
static void expandRelation(Oid relid, Alias *eref,
int rtindex, int sublevels_up,
bool include_dropped,
@@ -759,9 +759,9 @@ addRangeTableEntry(ParseState *pstate,
* Get the rel's OID. This access also ensures that we have an
* up-to-date relcache entry for the rel. Since this is typically the
* first access to a rel in a statement, be careful to get the right
* access level depending on whether we're doing SELECT FOR UPDATE.
* access level depending on whether we're doing SELECT FOR UPDATE/SHARE.
*/
lockmode = isForUpdate(pstate, refname) ? RowShareLock : AccessShareLock;
lockmode = isLockedRel(pstate, refname) ? RowShareLock : AccessShareLock;
rel = heap_openrv(relation, lockmode);
rte->relid = RelationGetRelid(rel);
@@ -1121,17 +1121,17 @@ addRangeTableEntryForJoin(ParseState *pstate,
}
/*
* Has the specified refname been selected FOR UPDATE?
* Has the specified refname been selected FOR UPDATE/FOR SHARE?
*/
static bool
isForUpdate(ParseState *pstate, char *refname)
isLockedRel(ParseState *pstate, char *refname)
{
/* Outer loop to check parent query levels as well as this one */
while (pstate != NULL)
{
if (pstate->p_forUpdate != NIL)
if (pstate->p_lockedRels != NIL)
{
if (linitial(pstate->p_forUpdate) == NULL)
if (linitial(pstate->p_lockedRels) == NULL)
{
/* all tables used in query */
return true;
@@ -1141,7 +1141,7 @@ isForUpdate(ParseState *pstate, char *refname)
/* just the named tables */
ListCell *l;
foreach(l, pstate->p_forUpdate)
foreach(l, pstate->p_lockedRels)
{
char *rname = strVal(lfirst(l));

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_type.c,v 1.73 2004/12/31 22:00:27 pgsql Exp $
* $PostgreSQL: pgsql/src/backend/parser/parse_type.c,v 1.74 2005/04/28 21:47:14 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -432,7 +432,7 @@ parseTypeString(const char *str, Oid *type_id, int32 *typmod)
stmt->sortClause != NIL ||
stmt->limitOffset != NULL ||
stmt->limitCount != NULL ||
stmt->forUpdate != NIL ||
stmt->lockedRels != NIL ||
stmt->op != SETOP_NONE)
goto fail;
if (list_length(stmt->targetList) != 1)