Search code examples
pythondatabasepostgresqlpsycopg2digital-ocean

Can't acces a postgresql table


Simple queries run for hours, but only for one table, what can be the cause?

I tried deleting it, obviously it got stalled too, and I can't do any other basic or complex queries for that table. I am at digitalocean, so I can't just do a restart on the DB. I imported this table from a URL with python and I forgot to close the psycopg2 connection before running the script so I had to terminate it, and maybe that could have caused the problem.

BTW. this is my personal database, I don't have other admins who could have locked me out or anything. I also looked at the logs, but there isn't any sign of unusual activities. Here is it though, originally its in German, I had to translate it.

09:18:35.644: PostgreSQL 14.2, compiled by Visual C++ build 1914, 64-bit launches

09:18:35.645: expected connections on IPv6 address "::", port 25060

09:18:35.645: expecting connections on IPv4 address "0.0.0.0", port 25060

09:18:35.691: database system was interrupted; last known action on 2022-06-16 23:54:18 CEST

09:18:36.137: database system was not shut down properly; automatic recovery in progress

09:18:36.140: Redo starts at 0/172B560

09:18:36.141: invalid record length at 0/172B598: 24 expected, 0 received

09:18:36.141: redo complete at 0/172B560 System usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s

09:18:36.164: database system is ready to accept connections

20:34:07.203: fast shutdown requested

20:34:07.208: any active transactions are aborted

20:34:07.232: background worker "logical replication launcher" (PID 15088) terminated with status 1

20:34:07.241: shut down

20:34:07.292: database system shut down


Solution

  • You probably have an orphaned idle process that has acquired a lock on the table which has not been released due to an uncommitted transaction -and will never be released since you have no ability to commit it.

    If simple queries like SELECT * FROM table are blocking, you most likely have an Access Exclusive Lock on the table from something like an ALTER/DROP statement that had run in the script that you cancelled.

    You can verify this by running:

    SELECT
        psa.pid,
        psa.query,
        psa.state,
        pg_locks.mode lock_mode,
        relation::regclass locked_relation
    
    FROM
      pg_locks
      JOIN pg_stat_activity psa on pg_locks.pid = psa.pid
    
    WHERE
      granted
      and 'my_schema.my_table'::regclass = relation
    

    Then you can kill any process that has a lock on your table:

    SELECT
        pg_terminate_backend(psa.pid),
    
    FROM
      pg_locks
      JOIN pg_stat_activity psa on pg_locks.pid = psa.pid
    
    WHERE
      granted
      and 'my_schema.my_table'::regclass = relation
    

    If you still have blocked queries, you can open a new connection while the blocked query is running, and execute the following:

    SELECT
        activity.pid pid,
        activity.state state,
        activity.query blocked_query,
        blocking_pid,
        blocking_activity.state blocking_state,
        blocking_activity.query blocking_query,
        locks.locked_relations blocking_relation_locks
    FROM
     pg_stat_activity activity
     JOIN LATERAL unnest(pg_blocking_pids(activity.pid)) blocking_pids(blocking_pid) ON TRUE
     JOIN pg_stat_activity blocking_activity ON (blocking_pids.blocking_pid = blocking_activity.pid)
     JOIN LATERAL (
         SELECT
            string_agg(locks.relation::regclass::text,',') locked_relations
         FROM
    
           pg_locks locks
    
         WHERE
             locks.pid = blocking_pid
    
         GROUP BY
           blocking_pid
    ) locks ON TRUE;
    

    This will show you every process that is blocking another process. Once you find the offending processes you can exterminate them using pg_terminate_backend(pid)

    If you want to kill every pid blocking another pid, you can run:

    SELECT 
      pg_terminate_backend(unnest(pg_blocking_pids(pid)))
    FROM
      pg_stat_activity 
    

    If don't have other critical processes running and want to eliminate every single database connection for the sake of simplicity, do:

    SELECT
      pg_terminate_backend(pg_stat_activity.pid)
    FROM
      pg_stat_activity
    WHERE
      --delete this line if you want to kill all connections on the whole cluster
      pg_stat_activity.datname = '<your database name>'
      AND pid <> pg_backend_pid();
    

    Once complete, you'll only have the connection on which you executed this query.

    If none of the above steps work it's possible -as Laurenz Albe pointed out in the comments -that you may have a prepared transaction that is locking your table. The locks that a prepared transaction creates can survive backend terminations -and even server restarts. They must be explicitly rolled back or committed.

    You can check if a prepared transaction is locking any of your tables with the following query

    SELECT
      ptx.transaction prep_tx_id,
      ptx.gid prep_tx_name,
      locks.mode lock_mode,
      locks.relation::regclass locked_relation
    FROM
      pg_prepared_xacts ptx
      JOIN pg_locks tx ON ptx.transaction = tx.transactionid and tx.locktype = 'transactionid'
      JOIN pg_locks locks ON tx.virtualtransaction = locks.virtualtransaction
    
    WHERE
      locks.granted
      and locks.relation = 'my_schema.my_table'::regclass
    

    You can then rollback the prepared transaction like:

    ROLLBACK PREPARED '<TRANSACTION_NAME>'