postgresql-fillfactor
fillfactor
fillfactor是在創建表的時候指定的參數,該參數是限制數據插入一頁時預留的空閑空間比例,對於數據庫表的默認值是100,索引默認值是90
table(默認100)
一個表的填充因子是一個10-100質檢的百分數。100(完全填滿)是默認值。設置較小的填充因子,insert操作會把表頁面只填滿到指定的百分比,剩余的空間留給頁面上行的更新。這就讓update有機會把一行的已更新版本放到在與原始版本相同的頁面上,這比把它放在一個不同的頁面上效率更高。對於不經常更新的表來說,設置為100是最好的選擇,如果更新頻繁設置較小的值更合適。這個參數對toast表不生效。
index(fillfactor默認90)
索引的填充因子是一個百分數,它決定索引方法將嘗試填充索引頁面的充滿程度。對於B-tree,在初始的索引構建過程中,葉子頁面會被填充至該百分數,B-tree默認的填充因子是90,可以設置為10-100的任何整數值。如果表是靜態的,那么填充因子100是最好的,這樣索引占用空間最小。對於更新頻繁的表,設置較小的值有利於最小化頁面分裂。
驗證
--創建表test_fill_1設置fillfactor=100
abase=# create table test_fill_1(n_id int,c_xm varchar(300)) with (fillfactor=100);
CREATE TABLE
--創建表test_fill_2設置fillfactor=80
abase=# create table test_fill_2(n_id int,c_xm varchar(300)) with (fillfactor=80);
CREATE TABLE
--添加主鍵
abase=# alter table test_fill_1 add primary key(n_id);
ALTER TABLE
abase=# alter table test_fill_2 add primary key(n_id);
ALTER TABLE
--初始化數據
abase=# insert into test_fill_1 select generate_series(1,1000000),'zhangsan'||generate_series(1,1000000);
INSERT 0 1000000
Time: 7067.047 ms
abase=# insert into test_fill_2 select generate_series(1,1000000),'zhangsan'||generate_series(1,1000000);
INSERT 0 1000000
Time: 6849.234 ms
--表分析
postgres=# vacuum analyze test_fill_1;
VACUUM
postgres=# vacuum analyze test_fill_2;
--查看表的頁數
abase=# select relpages,reltuples from pg_class where relname = 'test_fill_1';
relpages | reltuples
----------+-----------
6369 | 1e+06
(1 row)
abase=# select relpages,reltuples from pg_class where relname = 'test_fill_2';
relpages | reltuples
----------+-----------
7999 | 1e+06
(1 row)
--查看表結構
abase=# \d+ test_fill_1;
Table "public.test_fill_1"
Column | Type | Modifiers | Storage | Stats target | Description
--------+------------------------+-----------+----------+--------------+-------------
n_id | integer | | plain | |
c_xm | character varying(300) | | extended | |
Options: fillfactor=100
abase=# \d+ test_fill_2;
Table "public.test_fill_2"
Column | Type | Modifiers | Storage | Stats target | Description
--------+------------------------+-----------+----------+--------------+-------------
n_id | integer | | plain | |
c_xm | character varying(300) | | extended | |
Options: fillfactor=80
--查看表大小,fillfactor越大占用的空間越小
abase=# select pg_size_pretty(pg_relation_size('test_fill_1'));
pg_size_pretty
----------------
50 MB
(1 row)
Time: 1.251 ms
abase=# select pg_size_pretty(pg_relation_size('test_fill_2'));
pg_size_pretty
----------------
62 MB
(1 row)
Time: 0.995 ms
--查看主鍵大小
abase=# select pg_size_pretty(pg_relation_size('test_fill_1_pkey'));
pg_size_pretty
----------------
21 MB
(1 row)
abase=# select pg_size_pretty(pg_relation_size('test_fill_2_pkey'));
pg_size_pretty
----------------
21 MB
(1 row)
初始化數據耗時差別不大,fillfactor=80略快。設置了fillfactor=80的表占用空間更大。索引方面占用空間一樣。
更新數據
--1.更新test_fill_1
abase=# select ctid,* from test_fill_1 where n_id =1;
ctid | n_id | c_xm
-------+------+-----------
(0,1) | 1 | zhangsan1
(1 row)
abase=# update test_fill_1 set c_xm='李四' where n_id = 1;
UPDATE 1
--更新test_fill_1 fillfactor為100,更新后,數據插入到了最后一頁ctid為(6368,74)
abase=# select ctid,* from test_fill_1 where n_id =1;
ctid | n_id | c_xm
-----------+------+------
(6368,74) | 1 | 李四
(1 row)
--2.更新test_fill_2
abase=# select ctid,* from test_fill_2 where n_id =1;
ctid | n_id | c_xm
-------+------+-----------
(0,1) | 1 | zhangsan1
(1 row)
Time: 1.392 ms
abase=# update test_fill_2 set c_xm='李四' where n_id = 1;;
UPDATE 1
--test_fill_2表的fillfactor為80,還剩余20%的空間可以利用,更新后數據是在第一頁插入了這條數據,ctid為 (0,149)還在第一頁
abase=# select ctid,* from test_fill_2 where n_id =1;
ctid | n_id | c_xm
---------+------+------
(0,149) | 1 | 李四
(1 row)
設置了fillfactor=100后,更新數據會在最后一頁插入一條數據
而設置fillfactor=80,數據會在當前頁插入一條數據
更新效率
--為了看出明顯的效果,先將autovacuum關閉掉
--更新test_fill_1的所有數據‘
更新的效率來看較小的fillfactor更新更快
abase=# update test_fill_1 set c_xm = c_xm||'x';
UPDATE 1000000
Time: 13035.901 ms
--更新test_fill_2的所有數據
abase=# update test_fill_2 set c_xm = c_xm||'x';
UPDATE 1000000
Time: 10162.411 ms
--再次全部更新
abase=# update test_fill_1 set c_xm = c_xm||'y';
UPDATE 1000000
Time: 11741.058 ms
abase=# update test_fill_2 set c_xm = c_xm||'y';
UPDATE 1000000
Time: 10838.738 ms
abase=# update test_fill_1 set c_xm = c_xm||'z';
UPDATE 1000000
Time: 14763.844 ms
abase=# update test_fill_2 set c_xm = c_xm||'z';
UPDATE 1000000
Time: 9392.977 ms
--經過三次全部更新來看,設置fillfactor=80時,更新的速度在10s左右。
--多次更新后,可以看到fillfactor=80的頁在更新時,還是會使用前面的頁的舊行。
abase=# select ctid,*from test_fill_1 limit 100;
ctid | n_id | c_xm
------------+--------+-----------------------
(6369,155) | 1 | zhangsan1xyzxxy
(6369,156) | 2 | zhangsan2xyzxxy
(6369,157) | 3 | zhangsan3xyzxxy
(6369,158) | 4 | zhangsan4xyzxxy
(6369,159) | 5 | zhangsan5xyzxxy
(6369,160) | 6 | zhangsan6xyzxxy
(6369,161) | 7 | zhangsan7xyzxxy
(6369,162) | 8 | zhangsan8xyzxxy
(6369,163) | 9 | zhangsan9xyzxxy
(6369,164) | 10 | zhangsan10xyzxxy
(6369,165) | 11 | zhangsan11xyzxxy
(6369,166) | 12 | zhangsan12xyzxxy
(6369,167) | 13 | zhangsan13xyzxxy
abase=# select ctid,*from test_fill_2 limit 100;
ctid | n_id | c_xm
---------+------+---------------------
(0,158) | 1 | zhangsan1xyzxxy
(0,159) | 2 | zhangsan2xyzxxy
(0,160) | 3 | zhangsan3xyzxxy
(0,161) | 4 | zhangsan4xyzxxy
(0,162) | 5 | zhangsan5xyzxxy
(0,163) | 6 | zhangsan6xyzxxy
(0,164) | 7 | zhangsan7xyzxxy
(0,165) | 8 | zhangsan8xyzxxy
(0,166) | 9 | zhangsan9xyzxxy
(0,167) | 10 | zhangsan10xyzxxy
(0,168) | 11 | zhangsan11xyzxxy
(0,169) | 12 | zhangsan12xyzxxy
(0,170) | 13 | zhangsan13xyzxxy
(0,171) | 14 | zhangsan14xyzxxy
(0,172) | 15 | zhangsan15xyzxxy
--更新小范圍數據,test_fill_2效果很明顯
abase=# update test_fill_1 set c_xm = c_xm||'xx' where n_id>1000 and n_id <2000;
UPDATE 999
Time: 28.306 ms
abase=# update test_fill_2 set c_xm = c_xm||'xx' where n_id>1000 and n_id <2000;
UPDATE 999
Time: 13.577 ms
在update(全量)的時候fillfactor=80的效率更高,少量數據更新的時候低fillfactor效果更明顯。
再次update以后,可以看到fillfactor=80的頁在更新時,還是會使用更新到前面的頁的舊行。(沒有autovacuum的情況下仍然更新可以使用標記為刪除的行)
更新后索引大小
--test_fill_1表的所以更大
postgres=# select pg_size_pretty(pg_relation_size('test_fill_1_pkey'));
pg_size_pretty
----------------
66 MB
(1 row)
Time: 0.870 ms
postgres=# select pg_size_pretty(pg_relation_size('test_fill_2_pkey'));
pg_size_pretty
----------------
43 MB
(1 row)
Time: 0.782 ms
可以看到設置了fillfactor=80表的索引比fillfactor=100的索引要小,而fillfactor=100膨脹的更快,這是為什么呢?請往下看
Heap-Only Tuples(hot)
在PG中因為多版本功能的原因,當更新一行時,實際上舊行並未被刪除,只是插入一條新行。如果這個表上有索引,而更新的字段不是索引的鍵值時,由於新行的物理位置發生了變化,因此仍然需要更新索引,這將導致性能下降。為了解決這個問題PostgreSQL自8.3版本后,引入了一個名為Heap-Only Tuple的新技術,簡稱HOT。使用HOT技術之后,如果更新后的新行與舊行在同一個數據塊內,舊行會有一個指針,指向新行,這樣就不必更新索引了,當從索引訪問到數據行時,會根據這個指針找到新行。
HOT詳細說明見下圖,圖中表上有一個索引,其中“索引項n”指向數據塊的第3行。
更新第三行后,因為有用HOT技術,所以索引項仍然指向原先的舊數據(第3行),而第3行舊數據中有一個指針指向新數據(第6行),如下圖:
注意,如果在原先的數據塊中無法放下新行,就不能使用HOT技術了,即HOT技術中的行間指針只能在一個數據塊內,不能跨數據塊。所以為了使用HOT技術,應該在數據塊中留出較多的空閑空間,方法是把表的填充因子(fillfactor)設置為一個合適的值。
Fillfactor參數的意思是插入數據時,塊數據的空間占用率達到這個比率后,就不在插入數據了,默認值為100,表示塊中不留存空間,數據全部填滿數據塊。
當有空閑空間的時候,更新可能會走hot更新,數據是在一頁內變動的,索引不會變化。所以前面fillfactor=80在更新后索引要比fillfactor=100小。
總結
1.初始化數據耗時差別不大,設置了fillfactor=80的表占用空間更大,初始化時索引方面占用空間一樣。
2.設置了fillfactor=100后,更新數據會在最后一頁插入一條數據,而設置fillfactor=80,數據會在當前頁空閑空間插入一條數據
3.在更新較頻繁的表設置合適的fillfactor可以提高更新效率,因為有hot技術,減小索引膨脹
4.在update(全量)的時候fillfactor=80的效率更高,少量數據更新的時候低fillfactor效果更明顯。
5.多次update以后,可以看到fillfactor=80時,還是會使用前面的頁的舊行。(如果autovacuum沒有及時清理的情況下更新可以使用標記為刪除的行)