π§ What Youβll Learn
- β Why DDL may not block DML in YugabyteDB by default
- β How behavior changes when table-level locks are enabled (2025.1.1+)
- β How to prove blocking using
pg_locks - β What this means for PostgreSQL compatibility expectations
β Behavior When Table-Level Locks Are NOT Enabled (Default)
By default, table-level locks are disabled in YugabyteDB.
Letβs attempt a classic PostgreSQL-style blocking scenario:
BEGIN;
LOCK TABLE test IN ACCESS EXCLUSIVE MODE;
Result:
ERROR: ACCESS EXCLUSIVE not supported yet
Thatβs expected… Without table-level locks:
β
ACCESS EXCLUSIVEis not supportedβ Full PostgreSQL table-level lock semantics are not enforced
β Important: DDL Does NOT Block INSERTs in This Mode
Even if you run a DDL operation such as:
DROP INDEX test_c2_idx;
while another session runs:
INSERT INTO test SELECT 1, 'A';
The INSERT does not block. DML continues during DDL operations when table-level locks are disabled.
This behavior is documented in:
π‘ What This Means
- β PostgreSQL relies on
ACCESS EXCLUSIVElocks for non-concurrent DDL. - β YugabyteDB (without table locks) allows higher concurrency.
- β But traditional Postgres-style blocking behavior does not occur.
β Behavior When Table-Level Locks ARE Enabled (2025.1.1+)
Now letβs enable table-level locks and repeat the test.
Test Environment:
β YugabyteDB 2025.2.0
β Table-level locks enabled
π Enabling Table-Level Locks
Table-level locks are disabled by default.
See official docs:
π§ͺ Demo Scenario
Example table and index:
CREATE test (c1 INT PRIMARY KEY, c2 TEXT);
CREATE INDEX test_c2_idx ON test(c2);
We use three sessions:
| π§© Session | π― Purpose |
|---|---|
LOCK_HOLDER | Holds an ACCESS EXCLUSIVE lock on testΒ (simulates the blocking condition). |
DDL_SESSION | Runs the DDL statement (example: DROP INDEX) and demonstrates it waiting. |
DML_SESSION | Attempts concurrent DML (example: INSERT) and demonstrates it being blocked. |
application_name in each session so the statements are easy to identify inpg_stat_activity, pg_locks, and ASH.π Session #1 – Hold the Lock
SET application_name = 'LOCK_HOLDER';
BEGIN;
LOCK TABLE test IN ACCESS EXCLUSIVE MODE;
SELECT pg_sleep(60);
-- keep the transaction open until you're done observing blocking
π Session #2 – Run DDL
SET application_name = 'DDL_SESSION';
DROP INDEX testc2_idx;
β Session #3 – Run DML
SET application_name = 'DML_SESSION';
INSERT INTO test SELECT 1, 'A';
Now both DDL and INSERT hang until the lock is released.
π¨ The Smoking Gun: pg_locks
SELECT
relation::regclass AS rel,
mode,
granted,
waitstart,
ybdetails->>'transactionid' AS yb_txn_id,
CASE
WHEN mode='AccessExclusiveLock' AND granted THEN 'lock held (blocks reads+writes)'
WHEN mode='AccessExclusiveLock' AND NOT granted THEN 'DDL waiting for lock'
WHEN mode='RowExclusiveLock' AND NOT granted THEN 'DML waiting (blocked)'
ELSE 'other'
END AS interpretation
FROM pg_locks
WHERE relation='public.test'::regclass
ORDER BY granted DESC, mode, waitstart;
Example output:
. rel | mode | granted | waitstart | yb_txn_id | interpretation
--------+---------------------+---------+-------------------------------+--------------------------------------+---------------------------------
test | AccessExclusiveLock | t | | d2156b92-7a30-4e80-b7d6-6008fecc08ae | lock held (blocks reads+writes)
test | AccessExclusiveLock | f | 2026-02-12 15:04:55.470879+00 | fa779eb5-2d59-4d78-8967-7bd33f5f0bd2 | DROP INDEX waiting for lock
test | RowExclusiveLock | f | 2026-02-12 15:04:57.647031+00 | 4598a5ee-c92e-478b-ab03-ffac63d62341 | INSERT waiting (DML blocked)
(3 rows)
π¬ What This Proves
- β The table is locked with
AccessExclusiveLock. - β The DDL statement is waiting for that same lock.
- β The INSERT is blocked waiting for
RowExclusiveLock. - β With table-level locks enabled, DDL blocks DML.
π Optional: ASH Correlation
To confirm sustained blocking during the lock window:
WITH sessions AS (
SELECT pid, application_name, state, query
FROM pg_stat_activity
WHERE application_name IN ('LOCK_HOLDER','DDL_SESSION','DML_SESSION')
),
ash_summary AS (
SELECT
a.pid,
a.wait_event,
count(*) AS samples
FROM yb_active_session_history a
JOIN sessions s USING (pid)
GROUP BY a.pid, a.wait_event
),
ash_top AS (
SELECT DISTINCT ON (pid)
pid, wait_event, samples
FROM ash_summary
ORDER BY pid, samples DESC
)
SELECT
s.application_name,
s.state,
s.query,
a.wait_event,
a.samples
FROM sessions s
LEFT JOIN ash_top a USING (pid)
ORDER BY s.application_name;
Example output:
.application_name | pid | query | wait_event_class | wait_event_type | wait_event | samples | lock_holder_txn | drop_waitstart | interpretation
------------------+---------+-----------------------------------+------------------+-----------------+--------------+---------+--------------------------------------+-------------------------------+-------------------------------
DROP_INDEX | 1937487 | DROP INDEX test_c2_idx; | YSQLQuery | Cpu | OnCpu_Active | 198 | d2156b92-7a30-4e80-b7d6-6008fecc08ae | 2026-02-12 15:04:55.470879+00 | 2026-02-12 15:04:57.647031+00
INSERT_WAITER | 1937570 | INSERT INTO test SELECT 1, 'A'; | YSQLQuery | Cpu | OnCpu_Active | 196 | d2156b92-7a30-4e80-b7d6-6008fecc08ae | 2026-02-12 15:04:55.470879+00 | 2026-02-12 15:04:57.647031+00
LOCK_HOLDER | 1937471 | SELECT pg_sleep(60); | YSQLQuery | Timeout | PgSleep | 59 | d2156b92-7a30-4e80-b7d6-6008fecc08ae | 2026-02-12 15:04:55.470879+00 | 2026-02-12 15:04:57.647031+00
π¬ What This Proves
- β The DDL session and DML session were both sampled repeatedly during the same time window.
- β Neither statement completed while the ACCESS EXCLUSIVE lock was held.
- β The dominant wait classification confirms sustained activity during the blocking period.
- β This corroborates the
pg_locksevidence that DDL and DML were waiting on the same table-level lock.
In short: ASH confirms the blocking wasnβt momentary… it was sustained and observable.
π§ PostgreSQL vs YugabyteDB (Summary)
| π§ System / Mode | π Non-Concurrent DDL Behavior (e.g., DROP INDEX) |
|---|---|
| PostgreSQL | DDL typically takes an ACCESS EXCLUSIVE lock andblocks concurrent DML (INSERT/UPDATE/DELETE) until it completes. |
| YugabyteDB (table-level locks disabled) | ACCESS EXCLUSIVE table locks are not supported, andDDL can run while concurrent DML continues (no Postgres-style table lock blocking). |
| YugabyteDB (table-level locks enabled – 2025.1.1+) | Postgres-style table lock semantics are enforced for this scenario: DDL waits on AccessExclusiveLock and blocks concurrent DML. |
Use
pg_locks (and optionally ASH) to confirm whatβs happening on your cluster.π§© Conclusion
Whether DDL blocks concurrent DML in YugabyteDB depends on whether table-level locks are enabled.
β With table-level locks disabled (default), DDL such as
DROP INDEXdoes not block concurrent INSERT/UPDATE/DELETE operations.β With table-level locks enabled (2025.1.1+), PostgreSQL-style semantics apply, and DDL can block DML via
AccessExclusiveLock.
In PostgreSQL, the usual way to avoid blocking writes during an index drop is:
DROP INDEX CONCURRENTLY ...
However, YugabyteDB does not currently support DROP INDEX CONCURRENTLY.
That means once table-level locks are enabled, there is no built-in βconcurrent dropβ escape hatch.
So the decision becomes operational:
β Want maximum concurrency during DDL? β Leave table-level locks disabled
β Want PostgreSQL lock semantics? β Enable table-level locks
β Need non-blocking index drops with locks enabled? β Plan operationally (maintenance window, traffic control)
The real takeaway is not simply βDoes DDL block?β…Β itβs:
- What concurrency model do you want your cluster to enforce?
Have Fun!
