LOCK MANAGEMENT

In PostgreSQL, managing locks is essential for maintaining data consistency and preventing conflicts in a multi-user environment. PostgreSQL provides various lock types and mechanisms to control access to data.
Postgres have three locking mechanisms
  • Table-level lock
  • Row-level lock
  • Advisory lock

  • TABLE LEVEL LOCK:
    A table-level lock is a type of lock that is applied to an entire table. It means that when a transaction acquires a table-level lock on a particular table, it can control access to the entire table, preventing other transactions from acquiring certain types of locks on the same table simultaneously. There are two main types of table-level locks in PostgreSQL:

    Share Lock (SHARE MODE):
    A share lock on a table allows multiple transactions to acquire the lock simultaneously for reading purposes. Transactions holding a share lock can read the table but cannot acquire an exclusive lock on the same table until the share lock is released.
    Example:
    
        LOCK TABLE table_name IN SHARE MODE;
    
    Exclusive Lock (EXCLUSIVE MODE):
    An exclusive lock on a table prevents any other transactions from acquiring any lock on the same table. Transactions holding an exclusive lock have exclusive access to the table for both reading and writing.
    Example:
    
        LOCK TABLE table_name IN EXCLUSIVE MODE;
    
    It's important to note that table-level locks can potentially lead to performance issues and contention in a multi-user environment, as they restrict access to the entire table. In many cases, it's recommended to use row-level locks or other isolation mechanisms instead of table-level locks to minimize contention and allow for concurrent access to different parts of the table.

    ROW LEVEL LOCK :
    A row-level locks are a mechanism for controlling access to individual rows within a table. These locks are used to ensure the consistency of data in a multi-user environment by preventing conflicts when multiple transactions try to access or modify the same rows simultaneously. PostgreSQL provides two main types of row-level locks:

    FOR UPDATE :
    The FOR UPDATE lock is used when a transaction wants to update or delete specific rows. It prevents other transactions from acquiring FOR UPDATE or FOR SHARE locks on the same rows until the locking transaction is committed or rolled back.
    Example:
    
        SELECT * FROM table_name WHERE condition FOR UPDATE;
    
    FOR SHARE :
    The FOR SHARE lock is used when a transaction wants to read specific rows without the intention of modifying them. Multiple transactions can acquire FOR SHARE locks on the same rows simultaneously, but it prevents any of them from acquiring a conflicting FOR UPDATE lock until the locks are released.
    Example:
    
        SELECT * FROM table_name WHERE condition FOR SHARE;
    
    These locks can be applied to individual rows based on the specified conditions in the WHERE clause. The FOR UPDATE and FOR SHARE clauses can be used with various SELECT statements, including simple SELECT queries and more complex queries involving joins.
    It's important to note that row-level locks are automatically acquired by PostgreSQL in certain situations, even if you don't explicitly use the FOR UPDATE or FOR SHARE clauses. For example, when you perform an UPDATE, PostgreSQL implicitly acquires a FOR UPDATE lock on the affected rows.
    When using row-level locks, it's crucial to release the locks appropriately by committing or rolling back the transaction. Failing to release locks can lead to contention and may result in deadlocks.

    DEAD LOCK :
    A deadlock in PostgreSQL (or any database system) occurs when two or more transactions are blocked indefinitely, each waiting for the other to release a lock. In other words, each transaction holds a resource that the other transaction needs and vice versa, resulting in a circular dependency.

    PostgreSQL uses a two-phase locking protocol to manage locks: transactions first acquire locks and then release them. Deadlocks can occur when the transactions are not careful about the order in which they acquire locks, leading to a situation where each transaction is waiting for a resource held by the other.

    Here's a simplified example to illustrate how a deadlock can occur:
  • Transaction A acquires a lock on resource X.
  • Transaction B acquires a lock on resource Y.
  • Transaction A attempts to acquire a lock on resource Y (waits for B to release it).
  • Transaction B attempts to acquire a lock on resource X (waits for A to release it).
  • Now, both transactions are waiting for a resource that the other transaction holds, creating a deadlock.

    PostgreSQL has mechanisms to detect and resolve deadlocks, but it's also essential for application developers and database administrators to design transactions carefully to minimize the likelihood of deadlocks. Here are some tips to avoid deadlocks:

    A deadlock in PostgreSQL (or any database system) occurs when two or more transactions are blocked indefinitely, each waiting for the other to release a lock. In other words, each transaction holds a resource that the other transaction needs and vice versa, resulting in a circular dependency.

    PostgreSQL uses a two-phase locking protocol to manage locks: transactions first acquire locks and then release them. Deadlocks can occur when the transactions are not careful about the order in which they acquire locks, leading to a situation where each transaction is waiting for a resource held by the other.

    Here's a simplified example to illustrate how a deadlock can occur:
    Transaction A acquires a lock on resource X.
    Transaction B acquires a lock on resource Y.
    Transaction A attempts to acquire a lock on resource Y (waits for B to release it).
    Transaction B attempts to acquire a lock on resource X (waits for A to release it).
    Now, both transactions are waiting for a resource that the other transaction holds, creating a deadlock.

    PostgreSQL has mechanisms to detect and resolve deadlocks, but it's also essential for application developers and database administrators to design transactions carefully to minimize the likelihood of deadlocks. Here are some tips to avoid deadlocks:

    1. Lock Ordering :
    Enforce a consistent order in which locks are acquired. If all transactions follow the same order when acquiring locks, deadlocks are less likely to occur.

    2. Lock Timeout:
    Set reasonable lock timeouts for transactions. If a transaction cannot acquire a lock within a specified time, it can be programmed to release its locks and retry later.

    3. Transaction Design:
    Break transactions into smaller, more manageable units to reduce the duration of locks. This reduces the chance of collisions between transactions.

    4. Use Explicit Locks Wisely:
    If using explicit locks (e.g., FOR UPDATE), ensure that they are acquired and released appropriately within the transaction.

    VIEWS FOR DETECTING LOCKS
    The view pg_locks provides access to information about the locks held by open transactions within the database server
        postgres=> \d pg_locks 
                        View "pg_catalog.pg_locks"
            Column       |   Type   | Collation | Nullable | Default 
        --------------------+----------+-----------+----------+---------
        locktype           | text     |           |          | 
        database           | oid      |           |          | 
        relation           | oid      |           |          | 
        page               | integer  |           |          | 
        tuple              | smallint |           |          | 
        virtualxid         | text     |           |          | 
        transactionid      | xid      |           |          | 
        classid            | oid      |           |          | 
        objid              | oid      |           |          | 
        objsubid           | smallint |           |          | 
        virtualtransaction | text     |           |          | 
        pid                | integer  |           |          | 
        mode               | text     |           |          | 
        granted            | boolean  |           |          | 
        fastpath           | boolean  |           |          |
    
        postgres=# select locktype,database,relation,pid,mode from pg_locks ;
    
        postgres=# select pg_terminate_backend(pid);
        pg_terminate_backend 
        ----------------------
        t
        (1 row)
    
    PG_STAT_ACTIVITY :
    The pg_stat_activity view in PostgreSQL provides information about the current database sessions, including details about locks and the status of each session. This view is particularly useful for monitoring and managing locks in a PostgreSQL database.
    Here's an example of how you can use pg_stat_activity for lock management:

    Viewing Current Locks :
    You can query pg_stat_activity to see information about current locks held by active sessions. The waiting column indicates if a session is waiting for a lock.
        SELECT * FROM pg_stat_activity;
    
    This query will return a result set with information about each active session, including details about the state, query being executed, and any locks held or waited for.

    Filtering for Locks :
    You can filter the results to focus on sessions that are holding or waiting for locks. For example, to see only sessions that are currently waiting for a lock:
        select pid, 
            usename, 
            pg_blocking_pids(pid) as blocked_by, 
            query as blocked_query
        from pg_stat_activity
        where cardinality(pg_blocking_pids(pid)) > 0;
    
    Finding blocked processes and blocking queries
        SELECT
            activity.pid,
            activity.usename,
            activity.query,
            blocking.pid AS blocking_id,
            blocking.query AS blocking_query
        FROM pg_stat_activity AS activity
        JOIN pg_stat_activity AS blocking ON blocking.pid = ANY(pg_blocking_pids(activity.pid));
    
    Viewing Locks on Tables – pg_lock
        select 
            relname as relation_name, 
            query, 
            pg_locks.* 
        from pg_locks
        join pg_class on pg_locks.relation = pg_class.oid
        join pg_stat_activity on pg_locks.pid = pg_stat_activity.pid