1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-17 17:02:08 +03:00

Change ANALYZE to take ShareUpdateExclusiveLock not AccessShareLock on

the table being analyzed.  This prevents two ANALYZEs from running
concurrently on the same table and possibly suffering concurrent-update
failures while trying to store their results into pg_statistic.  The
downside is that a database-wide ANALYZE executed within a transaction
block will hold ShareUpdateExclusiveLock on many tables simultaneously,
which could lead to concurrency issues or even deadlock against another
such ANALYZE.  However, this seems a corner case of less importance
than getting unexpected errors from a foreground ANALYZE when autovacuum
elects to analyze the same table concurrently.  Per discussion.
This commit is contained in:
Tom Lane
2006-09-17 22:50:31 +00:00
parent 2e5e856f6b
commit da7540b9d1
2 changed files with 193 additions and 194 deletions

View File

@ -1,4 +1,4 @@
<!-- $PostgreSQL: pgsql/doc/src/sgml/mvcc.sgml,v 2.60 2006/09/16 00:30:14 momjian Exp $ --> <!-- $PostgreSQL: pgsql/doc/src/sgml/mvcc.sgml,v 2.61 2006/09/17 22:50:31 tgl Exp $ -->
<chapter id="mvcc"> <chapter id="mvcc">
<title>Concurrency Control</title> <title>Concurrency Control</title>
@ -556,8 +556,7 @@ SELECT SUM(value) FROM mytab WHERE class = 2;
</para> </para>
<para> <para>
The commands <command>SELECT</command> and The <command>SELECT</command> command acquires a lock of this mode on
<command>ANALYZE</command> acquire a lock of this mode on
referenced tables. In general, any query that only reads a table referenced tables. In general, any query that only reads a table
and does not modify it will acquire this lock mode. and does not modify it will acquire this lock mode.
</para> </para>
@ -622,8 +621,8 @@ SELECT SUM(value) FROM mytab WHERE class = 2;
</para> </para>
<para> <para>
Acquired by <command>VACUUM</command> (without <option>FULL</option>) Acquired by <command>VACUUM</command> (without <option>FULL</option>),
and by <command>CREATE INDEX CONCURRENTLY</>. <command>ANALYZE</>, and <command>CREATE INDEX CONCURRENTLY</>.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -996,8 +995,8 @@ UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222;
<para> <para>
Short-term share/exclusive page-level locks are used for Short-term share/exclusive page-level locks are used for
read/write access. Locks are released immediately after each read/write access. Locks are released immediately after each
index row is fetched or inserted. However, note that GIN index index row is fetched or inserted. However, note that a GIN index
usually requires several inserts per one table row. usually requires several inserts for each table row.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/analyze.c,v 1.97 2006/08/18 16:09:08 tgl Exp $ * $PostgreSQL: pgsql/src/backend/commands/analyze.c,v 1.98 2006/09/17 22:50:31 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -129,10 +129,14 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt)
CHECK_FOR_INTERRUPTS(); CHECK_FOR_INTERRUPTS();
/* /*
* Open the relation, getting only a read lock on it. If the rel has * Open the relation, getting ShareUpdateExclusiveLock to ensure that
* been dropped since we last saw it, we don't need to process it. * two ANALYZEs don't run on it concurrently. (This also locks out
* a concurrent VACUUM, which doesn't matter much at the moment but
* might matter if we ever try to accumulate stats on dead tuples.)
* If the rel has been dropped since we last saw it, we don't need
* to process it.
*/ */
onerel = try_relation_open(relid, AccessShareLock); onerel = try_relation_open(relid, ShareUpdateExclusiveLock);
if (!onerel) if (!onerel)
return; return;
@ -147,7 +151,7 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt)
ereport(WARNING, ereport(WARNING,
(errmsg("skipping \"%s\" --- only table or database owner can analyze it", (errmsg("skipping \"%s\" --- only table or database owner can analyze it",
RelationGetRelationName(onerel)))); RelationGetRelationName(onerel))));
relation_close(onerel, AccessShareLock); relation_close(onerel, ShareUpdateExclusiveLock);
return; return;
} }
@ -162,7 +166,7 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt)
ereport(WARNING, ereport(WARNING,
(errmsg("skipping \"%s\" --- cannot analyze indexes, views, or special system tables", (errmsg("skipping \"%s\" --- cannot analyze indexes, views, or special system tables",
RelationGetRelationName(onerel)))); RelationGetRelationName(onerel))));
relation_close(onerel, AccessShareLock); relation_close(onerel, ShareUpdateExclusiveLock);
return; return;
} }
@ -174,7 +178,7 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt)
*/ */
if (isOtherTempNamespace(RelationGetNamespace(onerel))) if (isOtherTempNamespace(RelationGetNamespace(onerel)))
{ {
relation_close(onerel, AccessShareLock); relation_close(onerel, ShareUpdateExclusiveLock);
return; return;
} }
@ -183,7 +187,7 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt)
*/ */
if (RelationGetRelid(onerel) == StatisticRelationId) if (RelationGetRelid(onerel) == StatisticRelationId)
{ {
relation_close(onerel, AccessShareLock); relation_close(onerel, ShareUpdateExclusiveLock);
return; return;
} }
@ -317,7 +321,7 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt)
0, 0); 0, 0);
vac_close_indexes(nindexes, Irel, AccessShareLock); vac_close_indexes(nindexes, Irel, AccessShareLock);
relation_close(onerel, AccessShareLock); relation_close(onerel, ShareUpdateExclusiveLock);
return; return;
} }
@ -444,7 +448,8 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt)
/* /*
* Close source relation now, but keep lock so that no one deletes it * Close source relation now, but keep lock so that no one deletes it
* before we commit. (If someone did, they'd fail to clean up the entries * before we commit. (If someone did, they'd fail to clean up the entries
* we made in pg_statistic.) * we made in pg_statistic. Also, releasing the lock before commit would
* expose us to concurrent-update failures in update_attstats.)
*/ */
relation_close(onerel, NoLock); relation_close(onerel, NoLock);
} }
@ -1079,14 +1084,9 @@ compare_rows(const void *a, const void *b)
* Note analyze_rel() has seen to it that we won't come here when * Note analyze_rel() has seen to it that we won't come here when
* vacuuming pg_statistic itself. * vacuuming pg_statistic itself.
* *
* Note: if two backends concurrently try to analyze the same relation, * Note: there would be a race condition here if two backends could
* the second one is likely to fail here with a "tuple concurrently * ANALYZE the same table concurrently. Presently, we lock that out
* updated" error. This is slightly annoying, but no real harm is done. * by taking a self-exclusive lock on the relation in analyze_rel().
* We could prevent the problem by using a stronger lock on the
* relation for ANALYZE (ie, ShareUpdateExclusiveLock instead
* of AccessShareLock); but that cure seems worse than the disease,
* especially now that ANALYZE doesn't start a new transaction
* for each relation. The lock could be held for a long time...
*/ */
static void static void
update_attstats(Oid relid, int natts, VacAttrStats **vacattrstats) update_attstats(Oid relid, int natts, VacAttrStats **vacattrstats)
@ -1202,7 +1202,7 @@ update_attstats(Oid relid, int natts, VacAttrStats **vacattrstats)
else else
{ {
/* No, insert new tuple */ /* No, insert new tuple */
stup = heap_formtuple(sd->rd_att, values, nulls); stup = heap_formtuple(RelationGetDescr(sd), values, nulls);
simple_heap_insert(sd, stup); simple_heap_insert(sd, stup);
} }