前言
最近這段時間一直使用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='表名';
三 觸發器
如果一張表有觸發器,你往上插入數據就會非常慢。所以要刪除后插入在創建

-
查看觸發器 :
SELECT * FROM pg_trigger;
-
查詢某個表的觸發器
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 掉
