Search code examples
postgresqlcataloginformation-schema

Why does this query deadlock?


I have an application that reads the structure of an existing PostgreSQL 9.1 database, compares it against a "should be" state and updates the database accordingly. That works fine, most of the time. However, I had several instances now when reading the current database structure deadlocked. The query responsible reads the existing foreign keys:

SELECT tc.table_schema, tc.table_name, tc.constraint_name, kcu.column_name,
       ccu.table_schema, ccu.table_name, ccu.column_name
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
     ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu
     ON ccu.constraint_name = tc.constraint_name
WHERE constraint_type = 'FOREIGN KEY'

Viewing the server status in pgAdmin shows this to be the only active query/transaction that's running on the server. Still, the query doesn't return.

The error is reproducible in a way: When I find a database that produces the error, it will produce the error every time. But not all databases produce the error. This is one mysterious bug, and I'm running out of options and ideas on what else to try or how to work around this. So any input or ideas are highly appreciated!

PS: A colleague of mine just reported he produced the same error using PostgreSQL 8.4.


Solution

  • I tested and found your query very slow, too. The root of this problem is that "tables" in information_schema are in fact complicated views to provide catalogs according to the SQL standard. In this particular case, matters are further complicated as foreign keys can be built on multiple columns. Your query yields duplicate rows for those cases which, I suspect, may be an undesired.

    Correlated subqueries with unnest, fed to ARRAY constructors avoid the problem in my query.

    This query yields the same information, just without duplicate rows and 100x faster. Also, I would venture to guarantee, without deadlocks.

    Only works for PostgreSQL, not portable to other RDBMSes.

    SELECT c.conrelid::regclass AS table_name
         , c.conname AS fk_name
         , ARRAY(SELECT a.attname
                 FROM   unnest(c.conkey) x
                 JOIN   pg_attribute a
                 ON     a.attrelid = c.conrelid AND a.attnum = x) AS fk_columns
         , c.confrelid::regclass AS ref_table
         , ARRAY(SELECT a.attname
                 FROM   unnest(c.confkey) x
                 JOIN   pg_attribute a
                 ON     a.attrelid = c.confrelid AND a.attnum = x) AS ref_columns
    FROM   pg_catalog.pg_constraint c
    WHERE  c.contype = 'f';
    -- ORDER  BY c.conrelid::regclass::text,2
    

    The cast to ::regclass yields table names as seen with your current search_path. May or may not be what you want. For this query to include the absolute path (schema) for every table name you can set the search_path like this:

    SET search_path = pg_catalog;
    SELECT ...
    

    To continue your session with your default search_path:

    RESET search_path;
    

    Related: