全部產品
Search
文件中心

Hologres:鎖以及排查鎖

更新時間:Jun 30, 2024

鎖是一種資料庫的訊號量管理機制,作用是保證SQL執行上不同事務的隔離性。本文為您介紹Hologres中的鎖及如何排查鎖。

背景資訊

當一個Query發起時,在Hologres中其經過的鏈路如下圖所示。Query鏈路Frontend解析Query,Query Engine產生執行計畫,然後Storage Engine讀取資料,整個鏈路的鎖有如下兩種。

  • Frontend Locks(前端鎖)

    Frontend為接入層,相容PostgreSQL協議,因此鎖也會相容PostgreSQL的部分鎖,主要用來管理Frontend(FE)中繼資料資訊。

  • Backend Locks(後端鎖)

    Backend是指Query Engine和Fixed Plan,將會享有Hologres內建的鎖,主要用於管理Storage Engine的Schema和資料。

鎖行為變更說明

  • 自Hologres V2.0版本開始,FE預設啟用了無鎖機制。當表的DDL、DQL發生衝突時(例如,在表A發生DDL變更期間有新的查詢請求),新的請求會直接報錯。如果您希望在衝突發生時不報錯,而是等待鎖釋放後再執行新的請求,您可以執行以下命令來設定GUC參數,關閉無鎖機制。

    ALTER database <db_name> SET hg_experimental_disable_pg_locks = off;
  • 從Hologres V2.1版本開始,對於無主鍵表的Bulkload操作進行了最佳化,改為僅擷取行鎖,而不再擷取表鎖。

鎖介紹

  • Frontend Locks(FE Locks)

    Hologres接入層Frontend相容PostgreSQL,因此在接入層的鎖與PostgreSQL相容。PostgreSQL提供了Table-level Lock、Row-level Lock和Advisory Lock三種鎖模式來控制並發的資料訪問。 目前Hologres相容的是Table-level Lock和Advisory Lock。

    說明

    Hologres目前不支援顯式的設定鎖命令以及Advisory Lock相關的UDF。

    • Table-level Lock

      • 分類

        Table-level Lock是指表鎖,表鎖包含如下種類。

        鎖名稱

        說明

        備忘

        ACCESS SHARE

        一般情況下只有SELECT命令會擷取相關表的這個鎖。

        不涉及。

        ROW SHARE

        只有SELECT FOR UPDATESELECT FOR SHARE這兩個命令需要擷取目標表的這個鎖,非目標表(比如JOIN關聯的其他表)僅擷取ACCESS SHARE鎖。

        Hologres不支援SELECT FOR UPDATESELECT FOR SHARE命令,因此無需關注。

        ROW EXCLUSIVE

        UPDATEDELETEINSERT 修改資料的命令需要擷取此鎖。

        需要結合Backend Locks一起關注。

        SHARE UPDATE EXCLUSIVE

        為了防止Vacuum和並發的Schema變更發生衝突的鎖,如下命令需要擷取此鎖。

        • lazy VACUUM 即非 Vacuum Full

        • ANALYZE

        • CREATE INDEX CONCURRENTLY

          說明

          Hologres使用此命令時不會擷取 SHARE UPDATE EXCLUSIVE鎖,而是與非 CONCURRENTLY形式的CREATE INDEX一樣擷取SHARE鎖。

        • CREATE STATISTICS,此命令Hologres不支援。

        • COMMENT ON

        • ALTER TABLE VALIDATE CONSTRAINT,此命令Hologres不支援。

        • ALTER TABLE SET/RESET (storage_parameter),Hologres僅支援使用此命令設定自己擴充的屬性以及PostgreSQL原生的 autovacuum_enabled屬性,不支援設定其他屬性;且設定上述屬性時不會擷取表的任何鎖。修改PostgreSQL內建的其他某些storage parameter參數需要擷取此鎖,詳情請參見ALTER TABLE

        • ALTER TABLE ALTER COLUMN SET/RESET options

        • ALTER TABLE SET STATISTICS,此命令Hologres不支援。

        • ALTER TABLE CLUSTER ON,此命令Hologres不支援。

        • ALTER TABLE SET WITHOUT CLUSTER,此命令Hologres不支援。

        重點關注ANALYZE命令。

        SHARE

        只有非concurrently的 CREATE INDEX 需要擷取此鎖。

        說明

        Hologres中建立JSON索引時會需要拿此鎖。

        關注 CREATE INDEX命令(建立JSON相關索引)。

        SHARE ROW EXCLUSIVE

        為了防止並發的資料修改的鎖,需要擷取此鎖的命令如下。

        • CREATE COLLATION,此命令Hologres不支援。

        • CREATE TRIGGER:,此命令Hologres不支援。

        • 部分ALTER TABLE命令。

          • DISABLE/ENABLE [ REPLICA | ALWAYS ] TRIGGER,此命令Hologres不支援。

          • ADD table_constraint,此命令Hologres不支援。

        Hologres不支援擷取此鎖的命令,所以無需關注。

        EXCLUSIVE

        僅有REFRESH MATERIALIZED VIEW CONCURRENTLY命令需要擷取此鎖。

        Hologres不支援REFRESH MATERIALIZED VIEW CONCURRENTLY命令,無需關注。

        ACCESS EXCLUSIVE

        完全獨佔訪問需要的鎖,與其他所有的鎖都衝突,需要擷取此鎖的命令如下。

        • DROP TABLE

        • TRUNCATE TABLE

        • REINDEX,此命令Hologres不支援。

        • CLUSTER,此命令Hologres不支援。

        • VACUUM FULL

        • REFRESH MATERIALIZED VIEW (without CONCURRENTLY),此命令Hologres不支援。

        • LOCK,顯式的LOCK命令,如果不指明具體的鎖類型,預設需要這個鎖;此命令Hologres不支援。

        • ALTER TABLE,除去上面明確提到擷取特定鎖的ALTER TABLE形式以外,其他的ALTER TABLE命令形式都預設擷取這個鎖。

        需要重點關注的鎖,在Hologres中執行的DDL操作都會擷取該鎖,與其他鎖衝突。

      • 逾時時間

        FE Locks沒有預設逾時時間,需要業務設定逾時時間,防止鎖等待時間過長,詳情請參見Query管理

      • 衝突關係

        鎖之間的衝突關係如下表所示,衝突意味著一個操作擷取鎖,其他動作擷取的鎖都需要等待。

        說明

        已開通表示不衝突,未開通表示衝突。

        Requested Lock Mode

        ACCESS SHARE

        ROW SHARE

        ROW EXCLUSIVE

        SHARE UPDATE EXCLUSIVE

        SHARE

        SHARE ROW EXCLUSIVE

        EXCLUSIVE

        ACCESS EXCLUSIVE

        ACCESS SHARE

        已開通

        已開通

        已開通

        已開通

        已開通

        已開通

        已開通

        未開通

        ROW SHARE

        已開通

        已開通

        已開通

        已開通

        已開通

        已開通

        未開通

        未開通

        ROW EXCLUSIVE

        已開通

        已開通

        已開通

        已開通

        未開通

        未開通

        未開通

        未開通

        SHARE UPDATE EXCLUSIVE

        已開通

        已開通

        已開通

        未開通

        未開通

        未開通

        未開通

        未開通

        SHARE

        已開通

        已開通

        未開通

        未開通

        已開通

        未開通

        未開通

        未開通

        SHARE ROW EXCLUSIVE

        已開通

        已開通

        未開通

        未開通

        未開通

        未開通

        未開通

        未開通

        EXCLUSIVE

        已開通

        未開通

        未開通

        未開通

        未開通

        未開通

        未開通

        未開通

        ACCESS EXCLUSIVE

        未開通

        未開通

        未開通

        未開通

        未開通

        未開通

        未開通

        未開通

    • Advisory Lock

      Advisory Lock指諮詢鎖,PostgreSQL提供了一種方法建立由應用定義其含義的鎖。Hologres大部分業務情況無需額外關注。

  • Backend Locks(BE Locks)

    • 分類

      在Hologres中BE Locks鎖分類如下。

      鎖分類

      鎖介紹

      Exclusive(X)

      獨佔鎖定(也稱互斥鎖),當事務需要修改一批或一條資料時申請獨佔鎖定,例如DML語句(DELETE、INSERT、UPDATE)。獨佔鎖定申請成功的前提是同一資源上沒有其他的共用鎖定或獨佔鎖定,當獨佔鎖定申請成功後,鎖資源上將不能同時有其他鎖。

      Shared(S)

      共用鎖定,當事務需要讀取一批或一條資料時申請共用鎖定,以免其他事務對將要讀取的資料做修改。同一個資源上允許存在多個共用鎖定,即允許DQL之間並發執行,因為DQL不會改變資源本身。

      Intent(I)

      意圖鎖定,這種類型的鎖通常用於表達鎖的階層,同一個資源上允許存在多個意圖鎖定,當申請成功後,該資源上就不能有獨佔鎖定。例如,當事務申請一行的獨佔鎖定時,它會同時申請表的意圖鎖定(表是比行更高層次的資源),以防止其他事務申請表的獨佔鎖定。

    • 逾時時間

      BE Locks逾時時間預設為5min,超過該時間則報錯。

    • 衝突關係

      BE Locks的衝突關係如下,衝突表示一個操作擷取鎖,其餘操作都需要等待鎖。

      說明

      已開通表示不衝突,未開通表示衝突。

      操作

      DROP

      ALTER

      SELECT

      UPDATE

      DETELE

      INSERT(包含INSERT ON CONFLICT)

      DROP

      未開通

      未開通

      未開通

      未開通

      未開通

      未開通

      ALTER

      未開通

      未開通

      已開通

      未開通

      未開通

      未開通

      SELECT

      未開通

      已開通

      已開通

      已開通

      已開通

      已開通

      UPDATE

      未開通

      未開通

      已開通

      未開通

      未開通

      未開通

      DETELE

      未開通

      未開通

      已開通

      未開通

      未開通

      未開通

      INSERT(包含INSERT ON CONFLICT)

      未開通

      未開通

      已開通

      未開通

      未開通

      未開通

鎖的作用範圍

根據鎖的分類,不同的鎖作用範圍不同。

  • FE Locks

    FE Locks只會作用在表對象上,與表資料無關,且鎖只有成功或者卡住兩種狀態,卡住就代表有鎖衝突在等待鎖。

  • BE Locks

    BE Locks會作用在資料或者表結構上,因此在作用範圍上會分為表資料鎖、行資料鎖、表結構(Schema)鎖。

    • 表資料鎖:代表整個表的資料都需要擷取鎖,如果多個任務同時都需要擷取表資料鎖,會造成任務等待鎖現象,從而任務延遲。

    • 行資料鎖:指整行資料擷取鎖,在執行效率上會更高,其中走Fixed Plan加速SQL執行的Query都是行資料鎖或者表Schema鎖。

    • 表Schema鎖:即表結構鎖,當事務需要讀取或修改表結構時申請Schema鎖,大部分的事務都會申請Schema鎖。目前主要有如下類型的Schema鎖。

      • SchX:Schema獨佔鎖定,用於DDL語句,目前只支援DROP TABLE命令。

      • SchU:Schema更新鎖定,用於修改表結構的DDL語句包含ALTER TABLEset_table_property命令。

      • SchE:Schema存在鎖,使用者DML和DQL語句,確保讀寫資料期間表不會被刪除。

      說明
      • SchU是DDL鎖更細粒度的管控,允許ALTER TABLE期間DQL正常運行,無需等待;SchX是最粗粒度的DDL獨佔鎖定,所有的DDL、DML、DQL都會等待。

      • 如果Start Query耗時較長,則可能在等待BE Locks。

在Hologres中,其中常見命令作用的鎖範圍如下。已開通表示操作命令會擷取該鎖。

說明
  • 非Fixed Plan的寫入、更新、刪除都為Bulkload。

  • CREATE INDEX命令目前是指建立JSON相關索引。

  • DDL命令包括CREATEDROPALTER等。

操作/鎖範圍

表鎖

表資料鎖

行資料鎖

表Schema鎖

CREATE

已開通

不涉及

DROP

已開通

說明

一旦DROP命令擷取鎖,無法執行其他命令,其他命令會等待鎖,直到發現表被刪除了,則其他命令失敗。

說明:

不涉及

不涉及

已開通

說明

與其他動作都衝突。

ALTER

已開通

說明

與DROP命令一致。

不涉及

不涉及

已開通

說明

在擷取表Schema鎖的同時可以對錶執行SELECT命令。

SELECT

已開通

說明

在擷取表鎖的同時可以對錶執行INSERTUPDATEDELETE命令,只與DDL鎖衝突。

不涉及

不涉及

已開通

說明

執行SELECT命令時除了不能執行DROP命令,其他動作都可以做。

INSERT (包含INSERT ON CONFLICT)

已開通

說明

INSERT命令與 CREATE INDEX、DDL衝突。

  • Hologres V2.0及以前版本的Bulkload方式會擷取表資料鎖。

  • Hologres V2.1版本起,僅有主鍵表的Bulkload會擷取表資料鎖。

說明

Bulkload與Fixed Plan相互衝突。

如果是通過Fixed Plan方式則擷取行資料鎖。

Hologres V2.1版本起,無主鍵表的Bulkload也改為僅擷取行資料鎖。

說明
  • Hologres V2.0及以前版本不支援離線寫入和Fixed Plan即時寫入同時進行。

  • Hologres V2.1版本起支援無主鍵表的離線寫入和Fixed Plan即時寫入同時進行。

已開通

說明

與DDL、DML衝突。

UPDATE

已開通

說明

CREATE INDEX、DDL鎖衝突。

已開通

說明

與DDL、DML衝突。

DELETE

已開通

說明

CREATE INDEX、DDL鎖衝突。

已開通

說明

與DDL、DML衝突。

事務相關的鎖說明

Hologres目前僅支援DDL的明確交易,並不支援單純的DML事務,也不支援DDL和DML混合的事務。

  • Hologres不支援嵌套子事務。

  • 單純的DML事務雖然文法上可以通過,但是實際不支援原子提交和復原。

    如下DML即使insert成功,如果之後的update失敗了,insert的資料也不會被復原。

    begin;
    insert into t1(id, salary) values (1, 0);
    update t1 set salary = 0.1 where id = 1;
    commit;
  • 純DDL事務可以按預期的方式工作

    其中任何一行DDL命令失敗則會整個事務被復原。例如下面alter命令失敗時,前面createdrop操作會被復原。

    begin;
    create table t1(i int);
    drop table if exists t2;
    alter table t1 add column n text;
    commit;
  • DDL和DML命令混合的事務會被禁止

    如下樣本,當事務中包含DDL和DML命令時,DML命令就會報錯。

    begin;
    create table t1(i int);
    update t1 set i = 1 where i = 1;
    -- DML命令報錯
    ERROR:  UPDATE in ddl transaction is not supported now.
  • 明確交易中任何一個命令獲得的鎖都只會在整個事務結束(提交或復原)時才統一釋放。

    如下樣本,當對父表做alter操作時,會同時擷取父表(login_history)和子表(login_history_202001)的ACCESS EXCLUSIVE鎖, 但是這個命令執行完不會立即釋放對應的鎖,而是要等待最後的commit執行完(不管成功還是失敗)才會釋放鎖。若是一直不執行commit,則會一直保持鎖,這時若有其他對這個表的DDL操作,則會鎖表並報錯。

     -- suppose we have three tables
    create table temp1(i int, t text);
    create table login_history(ds text, user_id bigint, ts timestamptz) partition by list (ds);
    create table login_history_202001 partition of login_history for values in ('202001');
    
    begin;
    alter table login_history_s1 add column user_id bigint;
    drop table temp1;
    create table tx2(i int);
    commit;

FE鎖排查

可以通過如下步驟排查是否有FE的鎖,以及出現鎖之後如何解決:

  1. 如果Query耗時較長,可以通過wait_event_type欄位查詢當前Query是否有鎖。

    如下命令樣本,如果結果中wait_event_type欄位值為lock,說明該Query有FE的鎖。

    --2.0及其以上版本
    select query,state,query_id,transaction_id,pid,wait_event_type,wait_event,running_info,extend_info FROM hg_stat_activity where query_id = 200640xxxx;
    --返回結果
    ----------------+----------------------------------------------------------------
    query           | drop table test_order_table1;
    state           | active
    query_id        | 200640xxxx
    pid             | 123xx
    transaction_id  | 200640xxxx
    wait_event_type | Lock
    wait_event      | relation
    running_info    | {"current_stage":{"stage_duration_ms":47383,"stage_name":"PARSING"},"fe_id":1,"warehouse_id":0}+
                    |
    extend_info     | {}                                                                                                                                           +
    
    --1.3及以下版本
    select datname, pid, application_name, wait_event_type, state,query_start, query from pg_stat_activity where backend_type in ('client backend');
    --返回結果
    ----------------+----------------------------------------------------------------
    datname           | holo_poc
    pid               | 321xxx
    application_name  | PostgreSQL JDBC Driver
    wait_event_type   | lock
    state             | active
    query_start       |2023-04-20 14:31:46.989+08
    query             | delete from xxx
  2. 查看鎖的持有人。

    使用如下命令查看鎖的持有人:

    select * from pg_locks where pid = <pid>;

    pid為步驟1中返回結果中的pid

  3. 排查哪個進程在拿鎖。

    通過步驟2判斷出當前SQL有鎖,根據其oid(即表關係)通過以下命令查看是否有拿鎖,t代表true,代表正在拿鎖。

    -- 查看拿到了表鎖的進程資訊
    select pid from pg_locks where relation = <OID> and granted = 't';
  4. 排查拿鎖的Query。

    通過步驟3返回結果的PID,使用如下命令查詢真正拿鎖的Query。

    select * from pg_stat_activity where pid = <PID>;
  5. 釋放鎖。

    找到拿鎖的Query之後,使用如下命令可以直接將Query結束,以釋放鎖。

    select pg_cancel_backend(<pid>);

BE鎖排查

如果在hg_stat_activity視圖的be_lock_waitersbe_lock_converting_waiters欄位有資料,說明Query在後端有鎖或者被鎖,可以通過如下步驟排查。

  • 情況1:當前Query產生鎖,其餘Query在等該鎖釋放。

    通過以下SQL可以看到當前SQL是否有BE鎖,以及哪些Query被該SQL鎖住,等待其釋放鎖。

    select query_id, transaction_id, ((extend_info::json)->'be_lock_waiters'->>0)::text as be_lock_waiters 
    FROM hg_stat_activity as h where h.state = 'active' and ((extend_info::json)->'be_lock_waiters')::text != '';
    
    --返回結果
    ----------------+------------------
    query_id        | 10005xxx
    transaction_id  | 10005xxx
    be_lock_waiters | 13235xxx
  • 情況2:查看當前Query被哪個Query鎖住,等待它釋放鎖。

    通過如下SQL,查詢當前Query被誰拿著鎖,等待這個Query釋放鎖。

    select query_id, transaction_id, ((extend_info::json)->'be_lock_waiters')::text as be_lock_waiters 
    FROM hg_stat_activity as h where h.state = 'active' and ((extend_info::jsonb)->'be_lock_waiters')::jsonb ? '10005xxx';
    
    -[ RECORD 1 ]---+------------------------------------------
    query_id        | 200740017664xxxx
    transaction_id  | 200740017664xxxx
    be_lock_waiters | ["200640051468xxxx","200540035746xxxx"]

常見問題

  • 報錯:internal error: Cannot acquire lock in time, current owners: [(Transaction =302xxxx, Lock Mode = SchS|SchE|X)].

    • 可能原因:執行Query的表有被其他Query擷取了BE Locks(如報錯Lock Mode = SchS|SchE|X,則為Schema穩定鎖、存在鎖、獨佔鎖定),導致Query等待BE Locks逾時(5min)從而報錯。

    • 解決方案:報錯中的transactionTransaction =302xxxx對應Query ID,可以通過Query ID在慢Query日誌或者活躍Query中查看對應擷取鎖的Query。

  • 報錯:ERROR: Operation timed out: Wait schema version timeout.: Server current target schema version:xxx is late from request schema version: yyy

    • 可能原因:執行DDL後,會先在Frontend(FE)節點執行,再非同步在Storage Engine(SE)執行,當FE執行DDL結束後會對節點版本(Version)進行更新,若SE的DDL還沒執行完成,導致SE的版本比FE的版本低,此時Query會等待SE執行DDL,如果超過5min後SE仍然沒有執行完成,就會報錯等待Schema版本逾時。

    • 解決方案:

      • 先終止(Kill)掉等待鎖的DDL,然後重試Query。

      • 重啟執行個體(極端手段)。

  • 報錯:The requested table name: xxx (id: 10, version: 26) mismatches the version of the table (id: 10, version: 28) from server

    • 可能原因:執行DDL後,會先在Frontend(FE)單個節點執行,再非同步在Storage Engine(SE)執行。SE已經完成DDL並更新版本,但是因為FE節點較多,節點間Replay未完成,導致部分FE節點的版本低於SE,而Query正好在版本較低的FE中執行,從而出現報錯。

    • 解決方案:

      • 多重試幾次Query。

      • 若是超過幾分鐘還報錯建議重啟執行個體。

  • 報錯:internal error: Cannot find index full ID: 86xxx (table id: 20yy, index id: 1) in storages or it is deleting!

    • 可能原因:執行DROP Table或者Truncate Table時,對這個表進行DML(SELECT、DELETE等)需要擷取DDL鎖,導致Query需要等鎖,直到發現表被刪除了從而報錯。

    • 解決方案:執行DROPTRUNCATE命令時,不對錶執行其他Query。