以前在Oracle数据库上,我们经常讨论PCTFREE参数,这个参数指定了一个新数据块在插入数据时,默认保留多大比例的空间,用于VARCHAR等可变长字段的UPDATE。实际上这个参数在Oracle的用户中也不太受到重视,在一般负载不是很大的系统中,这个参数设置与否对应用性能的影响不大,使用Oracle默认的10%已经足够了。不过在某些特殊高负载、高并发的交易型系统中,因为某个热表的PCTFREE设置过小导致的性能问题也是常见的,每年的优化项目中,我们都能够遇到一些这样的案例。一般情况下,将PCTFREE调大一些人,然后MOVE一下表,这个问题就基本上能解决了。
在PostgreSQL数据库中,也有一个类似的参数,称为FILLFACTOR,这个参数控制PG数据库中的新的数据块插入数据时的限制,当某个数据块的使用率超过这个值的时候,该数据块不再接受新数据的插入,剩余的空间也是被用于UPDATE等需要额外空间的操作。
PG数据库的FILLFACTOR 的默认值为 100 (%)。因此,元组通常无间隙地存储在页面中。 和Oracle数据库类似,PG的FILLFACTOR 也可以针对索引。默认的100%可以让一个页面存储更多的记录行数,有利于顺序扫描,而将FILLFACTOR降低的好处是提高了以页面为单位的离散访问,因为在页面中预留了可以保证 UPDATE 语句进行更新的空闲空间。在另一方面,如果FILLFACTOR参数设置的较低,相同记录的表需要存储到更多的页面中,会增加读写IO的成本。对于经常UPDATE 可变长字段的表或者索引,FILLFACTOR 的默认值100%明显是不合适的,设置一个较低的FILLFACTOR会确保系统长期运行时的性能稳定。
实际上设置不合理的FILLFACTOR导致的后果与设置不合理的PCTFREE参数是类似的,在PG数据库中,不合理的FILLFACTOR参数会导致相关表在buffer_content LWLOCK上产生更多的等待,从而影响高并发系统的性能。
创建表时要设置FILLFACTOR,应该写到表的WITH子句中,表创建后也可以使用ALTER TABLE命令去进行修改,不过和Oracle类似的是,对已经存在的表进行参数修改,只会影响修改后新建的数据块,老的数据块不会自动重组。下面是建表和修改表时采用非默认FILLLFACTOR的例子。
|
下面我们通过一个实际的例子来验证一下这个参数,我们选取的是benchmark测试的场景,实际上我们的测试环境使用的是傲腾SSD盘,因此IO性能十分出色,这种硬件场景下,FILLFACTOR的影响会被缩小。另外benchmark测试场景优化的也比较好,FILLFACTOR的影响因素也不是太大。虽然如此,我们还是能从这两个测试场景中看出一些细微的差别。
首先我们使用默认的方式创建测试数据表,标准的benchmark测试建表脚本都是使用通用脚本的,因此FILLFACTOR都是使用默认值。从测试情况上看,在我们这个环境中,NewOrders的TPMC是267546,总的TPMC是594485。
通过perf命令采集的操作系统的2M事件上看,也没有发现特别严重的争用。
从等待事件采样上看,我们采用1秒采样一次,60次采样的汇总上看,里面产生了少量的buffer_content等待,我们还没有针对benchmark做相关的优化,因此目前的主要等待还在其他方面,比如lock_manager、transactionid两个等待占的比重最大,因此热快冲突还不算是本场景的最主要的问题。
接下来我们使用60%的FILLFACTOR再来做一次测试。
NewOrders的TPMC大概提高了4000,有一定的提高,不过提高并不大。
从同样采样频率和采样次数的等待事件分析上看,buffer_content等待只剩下1次了,比前面的19次少了不少,buffer_mapping的等待事件也消失了。这是因为FILLFACTOR调小之后,数据打散到更多的数据块中,数据块的争用减少了。
在实际应用场景中,对于存在少量热块冲突的热表和索引,降低FILLFACTOR参数的值可以有效的提升并发性能,如果热块冲突比较严重,那么使用HASH分区表会有更好的效果。不过如果某张表上经常还要做顺序扫描,那么采用HASH分区表可能会对顺序扫描产生较大影响,那么FILLFACTOR参数可能是一种折中的选择。具体采用何种策略,在实际应用中,还是需要综合权衡,选取一个能够两全其美的方案。