1. 程式人生 > 其它 >postgresql 資料庫 INSERT 或 UPDATE 大量資料時速度慢的原因分析

postgresql 資料庫 INSERT 或 UPDATE 大量資料時速度慢的原因分析

前言
最近這段時間一直使用pg 資料庫插入更新大量的資料,發現pg資料庫有時候插入資料非常慢,這裡我對此問題作出分析,找到一部分原因,和解決辦法。

一 死元祖過多


提起pg資料庫,由於他的構造,就不得不說他的元祖。

1.1 什麼是元祖?


在Postgresql做delete操作時,資料集(也叫做元組 (tuples))是沒有立即從資料檔案中移除的,僅僅是通過在行頭部設定xmax做一個刪除標記。update操作也是一樣的,在postgresql中可以看作是先delete再insert;

這是Postgresql MVCC的基本思想之一,因為它允許在不同程序之間只進行最小的鎖定就可以實現更大的併發性。這個MVCC實現的缺點當然是它會留下被標記刪除的 元組( dead tuples),即使在這些版本的所有事務完成之後。

1.2 死元祖過多的危害


如果不清理掉那些dead tuples(對任何事務都是不可見的)將會永遠留在資料檔案中,浪費磁碟空間,對於表來說,有過多的刪除和更新,dead tuples很容易佔絕大部分磁碟空間。而且dead tuples也會在索引中存在,更加加重磁碟空間的浪費。這是在PostgreSQL中常說的膨脹(bloat)。自然的,需要處理的資料查詢越多,查詢的速度就越慢。

1.3 查詢死元祖情況


1.3.1 查詢那些表的死元祖過多
1、查詢當前資料庫表已經達到自動清理條件的表及相關資訊

SELECT
    c.relname 表名,
    (current_setting('autovacuum_analyze_threshold')::NUMERIC(12,4))+(current_setting('autovacuum_analyze_scale_factor')::NUMERIC(12,4))*reltuples AS 自動分析閾值,
    (current_setting('autovacuum_vacuum_threshold')::NUMERIC(12,4))+(current_setting('autovacuum_vacuum_scale_factor')::NUMERIC(12,4))*reltuples AS 自動清理閾值,
    reltuples::DECIMAL(19,0) 活元組數,
    n_dead_tup::DECIMAL(19,0) 死元組數
FROM
    pg_class c 

LEFT JOIN pg_stat_all_tables d

    ON C.relname = d.relname
WHERE
    c.relname LIKE'tb%'  AND reltuples > 0
    AND n_dead_tup > (current_setting('autovacuum_analyze_threshold')::NUMERIC(12,4))+(current_setting('autovacuum_analyze_scale_factor')::NUMERIC(12,4))*reltuples;

  2、查詢當前正在進行自動清理的表及相關資訊

SELECT
    c.relname 物件名稱,
    l.pid 程序id,
    psa.STATE 查詢狀態,
    psa.query 執行語句,
    now( ) - query_start 持續時間
FROM
    pg_locks l
INNER JOIN pg_stat_activity psa ON ( psa.pid = l.pid )
LEFT OUTER JOIN pg_class C ON ( l.relation = C.oid )
WHERE psa.query like 'autovacuum%' and l.fastpath='f'
ORDER BY query_start asc;

  3、查詢自動清理的歷史統計資訊

SELECT
    relname 表名,
    seq_scan 全表掃描次數,
    seq_tup_read 全表掃描記錄數,
    idx_scan 索引掃描次數,
    idx_tup_fetch 索引掃描記錄數,
    n_tup_ins 插入的條數,
    n_tup_upd 更新的條數,
    n_tup_del 刪除的條數,
    n_tup_hot_upd 熱更新條數,
    n_live_tup 活動元組估計數,
    n_dead_tup 死亡元組估計數,
     last_vacuum 最後一次手動清理時間,
    last_autovacuum 最後一次自動清理時間,
    last_analyze 最後一次手動分析時間,
    last_autoanalyze 最後一次自動分析時間,
    vacuum_count 手動清理的次數,
    autovacuum_count 自動清理的次數,
     analyze_count 手動分析此表的次數,
    autoanalyze_count 自動分析此表的次數,
    ( CASE WHEN n_live_tup > 0 THEN n_dead_tup :: float8 / n_live_tup :: float8 ELSE 0 END ) :: NUMERIC ( 12, 2 ) AS "死/活元組的比例"
FROM
    pg_stat_all_tables
WHERE
    schemaname = 'public'
ORDER BY n_dead_tup::float8 DESC;

  

1.4 解決辦法

1.4.1 修改引數,提高效率

大家可以根據實際修改pg資料庫
具體引數介紹之後還會有一篇文章詳細介紹

1.4.2 手動清理

有時候自動清理往往會因為各種原因實際效果達不到預期,這時候我們需要對某些死元祖過多的表進行手動清理

手動資料表收縮

VACUUM FULL VERBOSE 模式名.表名;
VACUUM FULL VERBOSE ANALYZE 模式名.表名;

  結果如下

二 索引過多導致插入過慢

索引過多,雖會提高查詢速度,但是插入數度就很慢,在大資料插入前最好能看一下表的索引。如果索引過多,建議刪掉,插入或者更新資料後,再重新建索引。

查詢索引:

select * from pg_indexes where tablename='表名';   

  

三 觸發器

如果一張表有觸發器,你往上插入資料就會非常慢。所以要刪除後插入在建立

  1. 檢視觸發器 :

SELECT * FROM pg_trigger;

  

  1. 查詢某個表的觸發器

SELECT event_object_table
      ,trigger_name
      ,event_manipulation
      ,action_statement
      ,action_timing
FROM  information_schema.triggers
WHERE event_object_table = '表名'
ORDER BY event_object_table
     ,event_manipulation;

  

四 死鎖

資料插入慢或者停滯不前有可能是 死鎖

1 查詢等待與鎖的程序、語句等資訊

select w1.pid as 等待程序,
w1.mode as 等待鎖模式,
w2.usename as 等待使用者,
w2.query as 等待會話,
b1.pid as 鎖的程序,
b1.mode 鎖的鎖模式,
b2.usename as 鎖的使用者,
b2.query as 鎖的會話,
b2.application_name 鎖的應用,
b2.client_addr 鎖的IP地址,
b2.query_start 鎖的語句執行時間
from pg_locks w1
join pg_stat_activity w2 on w1.pid=w2.pid
join pg_locks b1 on w1.transactionid=b1.transactionid and w1.pid!=b1.pid
join pg_stat_activity b2 on b1.pid=b2.pid
where not w1.granted;

  2、殺死造成鎖的程序

--中斷造成鎖的session,回滾未提交事物

SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE pid='62560'

  

如果仍然不能殺死會話,可以在作業系統層面,kill 掉


為人:謙遜、激情、博學、審問、慎思、明辨、 篤行
學問:紙上得來終覺淺,絕知此事要躬行
為事:工欲善其事,必先利其器。
態度:道阻且長,行則將至;行而不輟,未來可期
轉載請標註出處!