KingbaseES 等待事件之DataFileRead

等待事件含义

IO:DataFileRead等待事件发生在会话连接等待后端进程从存储中读取所需页面,原因是该页面在共享内存中不可用或无法找到。

所有查询和数据操作(DML)操作都访问缓冲池中的页面,语句包括SELECT、UPDATE和DELETE等。例如,UPDATE可以从表或索引中读取页面。如果请求或更新的页面不在共享缓冲池中,则此读取可能导致IO:DataFileRead事件。

因为共享缓冲池是有限的,如果业务繁忙,它可能会被填满。在这种情况下,对不在内存中的页面的请求会迫使数据库从磁盘中读取块。如果IO:DataFileRead事件频繁发生,则共享缓冲池可能太小,无法容纳工作负载。对于读取大量结果集的SELECT查询来说,这个问题非常严重。

等待事件增加的原因

案例:IO:DataFileRead事件频繁导致的性能问题

背景:
某用户电子商务网站近期遇到了数据库性能瓶颈,用户反馈查询响应变慢,特别是在促销活动期间。通过监控工具发现IO:DataFileRead等待事件频繁发生,且等待时间持续增加。

问题分析:

连接峰值:促销活动期间,用户访问量激增,导致数据库连接数迅速上升,多个会话同时尝试读取大量的数据页面,增加了IO:DataFileRead的等待时间。

执行全表扫描的SELECT语句:通过查询sys_stat_statements视图,发现有几个SELECT查询正在对大型表执行顺序扫描,这些查询没有有效的谓词筛选条件,导致读取了大量的数据页面。

服务器资源匮乏:促销活动期间,由于用户访问量增大,服务器的CPU和磁盘I/O资源都被大量消耗,进一步加剧了IO:DataFileRead等待事件的发生。

案例解决方案:

优化查询:针对那些执行顺序扫描的SELECT查询进行了优化,添加了合适的谓词筛选条件和索引,减少了读取的数据页面数量。同时,对于那些无法优化的查询,建议业务团队考虑对表进行分区或使用其他数据结构(如物化视图)来减轻对单一大表的访问压力。

调整共享缓冲池大小:根据系统的实际负载情况,适当增加了共享缓冲池的大小,以便能够容纳更多的数据页面,减少了从磁盘读取数据页面的次数。注意不要消耗过多服务器内存,原则上shared_buffers内存占用不超过操作系统的四分之一。

限制连接数:为了减轻数据库的连接压力,建议在数据库层面限制最大连接数,并建议业务团队在应用程序层面也进行连接池管理,避免在促销活动期间产生过多的数据库连接。此举既可以减轻服务器压力,又可以避免连接峰值出现。

优化服务器资源:建议对服务器的硬件进行升级,增加内存,增加CPU和磁盘I/O的处理能力,提高系统的整体性能。

效果:
经过上述优化措施的实施,IO:DataFileRead等待事件的频率和等待时间都显著降低,数据库的性能得到了明显的提升。在后续的促销活动期间,用户反馈查询响应速度明显加快,系统能够稳定地支持大量的用户访问。

等待事件增加的其他原因

1、用于大型数据集的 CTAS 和 CREATE INDEX

CTAS是CREATE TABLE AS SELECT语句。如果使用大型数据集作为源运行CTAS,或在大型表上创建索引,则可能会发生IO:DataFileRead事件。创建索引时,数据库可能需要使用顺序扫描读取整个对象。当页面不在内存中时,生成此等待事件。

2、多个vacuum进程同时运行

vacuum进程可以手动或自动触发。当对一个表进行大量更新或删除操作时,由于maintenance_work_mem空间不足,vacuum操作会消耗IO,IO:DataFileRead等待事件会增加。表回收空间后,用于IO:DataFileRead的vacuum时间会减少。

3、引入大量数据

当业务变更时,应用程序接收大量数据时,ANALYZE操作可能会更频繁地发生。IO:DataFileRead等待事件会增加。

解决方法

检查等待事件的查询的谓词筛选条件

如何确定正在生成IO:DataFileRead等待事件的特定查询,可以使用以下方法找到特定查询:

例如扩展sys_stat_statements提供的视图,或者通过KWR报告中的top sql选项。

sys_stat_all_tables视图中 seq_scan,seq_tup_read数量很多。

sys_statio_all_tables视图中会显示heap_blks_read,idx_blks_read,toast_blks_read,tidx_blks_read正在增加。

在top sql中找到可疑的sql并运行EXPLAIN命令。在输出中,识别使用的扫描类型。注意顺序扫描并不一定表明表有问题。但是那些大量结果集使用顺序扫描的查询自然会产生更多的IO:DataFileRead事件,这时需要对sql调优。

将vacuum维护影响降至最低

vacuum和analyze等维护操作非常重要,不建议关闭,以下方法可以最大限度地减少这些影响:

  • 在非高峰时段手动运行维护操作。此方法可防止数据库达到自动操作的阈值,或针对性设置表级vacuum触发阈值,分散autovacuum进程工作时间。
  • 对于非常大的表,请考虑对表进行分区。分区可减少维护操作的开销。数据库仅需要维护子分区即可。
  • 引入大量数据时,请考虑禁用自动分析功能。

当以下公式成立时,autovacuum功能会自动触发。

sys_stat_user_tables.n_dead_tup > (sys_class.reltuples x autovacuum_vacuum_scale_factor) + autovacuum_vacuum_threshold

1、查找占用不必要空间的表

若查找占用过多空间的表,请运行以下查询。查询仅返回有关用户角色有权读取的那些表的信息。

WITH report AS (
   SELECT   schemaname
           ,tblname
           ,n_dead_tup
           ,n_live_tup
           ,block_size*tblpages AS real_size
           ,(tblpages-est_tblpages)*block_size AS extra_size
           ,CASE WHEN tblpages - est_tblpages > 0
              THEN 100 * (tblpages - est_tblpages)/tblpages::float
              ELSE 0
            END AS extra_ratio, fillfactor, (tblpages-est_tblpages_ff)*block_size AS bloat_size
           ,CASE WHEN tblpages - est_tblpages_ff > 0
              THEN 100 * (tblpages - est_tblpages_ff)/tblpages::float
              ELSE 0
            END AS bloat_ratio
           ,is_na
    FROM (
           SELECT  ceil( reltuples / ( (block_size-page_hdr)/tpl_size ) ) + ceil( toasttuples / 4 ) AS est_tblpages
                  ,ceil( reltuples / ( (block_size-page_hdr)*fillfactor/(tpl_size*100) ) ) + ceil( toasttuples / 4 ) AS est_tblpages_ff
                  ,tblpages
                  ,fillfactor
                  ,block_size
                  ,tblid
                  ,schemaname
                  ,tblname
                  ,n_dead_tup
                  ,n_live_tup
                  ,heappages
                  ,toastpages
                  ,is_na
             FROM (
                    SELECT ( 4 + tpl_hdr_size + tpl_data_size + (2*ma)
                               - CASE WHEN tpl_hdr_size%ma = 0 THEN ma ELSE tpl_hdr_size%ma END
                               - CASE WHEN ceil(tpl_data_size)::int%ma = 0 THEN ma ELSE ceil(tpl_data_size)::int%ma END
                           ) AS tpl_size
                           ,block_size - page_hdr AS size_per_block
                           ,(heappages + toastpages) AS tblpages
                           ,heappages
                           ,toastpages
                           ,reltuples
                           ,toasttuples
                           ,block_size
                           ,page_hdr
                           ,tblid
                           ,schemaname
                           ,tblname
                           ,fillfactor
                           ,is_na
                           ,n_dead_tup
                           ,n_live_tup
                          FROM (
                                SELECT  tbl.oid                       AS tblid
                                       ,ns.nspname                    AS schemaname
                                       ,tbl.relname                   AS tblname
                                       ,tbl.reltuples                 AS reltuples
                                       ,tbl.relpages                  AS heappages
                                       ,coalesce(toast.relpages, 0)   AS toastpages
                                       ,coalesce(toast.reltuples, 0)  AS toasttuples
                                       ,psat.n_dead_tup               AS n_dead_tup
                                       ,psat.n_live_tup               AS n_live_tup
                                       ,24                            AS page_hdr
                                       ,current_setting('block_size')::numeric AS block_size
                                       ,coalesce(substring( array_to_string(tbl.reloptions, ' ') FROM 'fillfactor=([0-9]+)')::smallint, 100) AS fillfactor
                                       ,CASE WHEN version()~'mingw32' OR version()~'64-bit|x86_64|ppc64|ia64|amd64' THEN 8 ELSE 4 END        AS ma
                                       ,23 + CASE WHEN MAX(coalesce(null_frac,0)) > 0 THEN ( 7 + count(*) ) / 8 ELSE 0::int END              AS tpl_hdr_size
                                       ,sum( (1-coalesce(s.null_frac, 0)) * coalesce(s.avg_width, 1024) )                                    AS tpl_data_size
                                       ,bool_or(att.atttypid = 'pg_catalog.name'::regtype) OR count(att.attname) <> count(s.attname)         AS is_na
                                  FROM  pg_attribute       AS att
                                  JOIN  pg_class           AS tbl    ON (att.attrelid = tbl.oid)
                                  JOIN  pg_stat_all_tables AS psat   ON (tbl.oid = psat.relid)
                                  JOIN  pg_namespace       AS ns     ON (ns.oid = tbl.relnamespace)
                             LEFT JOIN  pg_stats           AS s      ON (s.schemaname=ns.nspname AND s.tablename = tbl.relname AND s.inherited=false AND s.attname=att.attname)
                             LEFT JOIN  pg_class           AS toast  ON (tbl.reltoastrelid = toast.oid)
                                 WHERE  att.attnum > 0
                                   AND  NOT att.attisdropped
                                   AND  tbl.relkind = 'r'
                              GROUP BY  tbl.oid, ns.nspname, tbl.relname, tbl.reltuples, tbl.relpages, toastpages, toasttuples, fillfactor, block_size, ma, n_dead_tup, n_live_tup
                              ORDER BY  schemaname, tblname
                           ) AS s
                 ) AS s2
       ) AS s3
 ORDER BY bloat_size DESC
)
  SELECT * 
    FROM report 
   WHERE bloat_ratio != 0
 -- AND schemaname = 'public'
 -- AND tblname = 'pgbench_accounts'
;

-- WHERE NOT is_na
--   AND tblpages*((pst).free_percent + (pst).dead_tuple_percent)::float4/100 >= 1

2、查找消耗不必要空间的索引

若要查找占用不必要空间的索引,请运行以下查询。

SELECT current_database(), nspname AS schemaname, tblname, idxname, bs*(relpages)::bigint AS real_size,
  bs*(relpages-est_pages)::bigint AS extra_size,
  100 * (relpages-est_pages)::float / relpages AS extra_ratio,
  fillfactor, bs*(relpages-est_pages_ff) AS bloat_size,
  100 * (relpages-est_pages_ff)::float / relpages AS bloat_ratio,
  is_na
  -- , 100-(sub.pst).avg_leaf_density, est_pages, index_tuple_hdr_bm, 
  -- maxalign, pagehdr, nulldatawidth, nulldatahdrwidth, sub.reltuples, sub.relpages 
  -- (DEBUG INFO)
FROM (
  SELECT coalesce(1 +
       ceil(reltuples/floor((bs-pageopqdata-pagehdr)/(4+nulldatahdrwidth)::float)), 0 
       -- ItemIdData size + computed avg size of a tuple (nulldatahdrwidth)
    ) AS est_pages,
    coalesce(1 +
       ceil(reltuples/floor((bs-pageopqdata-pagehdr)*fillfactor/(100*(4+nulldatahdrwidth)::float))), 0
    ) AS est_pages_ff,
    bs, nspname, table_oid, tblname, idxname, relpages, fillfactor, is_na
    -- , stattuple.pgstatindex(quote_ident(nspname)||'.'||quote_ident(idxname)) AS pst, 
    -- index_tuple_hdr_bm, maxalign, pagehdr, nulldatawidth, nulldatahdrwidth, reltuples 
    -- (DEBUG INFO)
  FROM (
    SELECT maxalign, bs, nspname, tblname, idxname, reltuples, relpages, relam, table_oid, fillfactor,
      ( index_tuple_hdr_bm +
          maxalign - CASE -- Add padding to the index tuple header to align on MAXALIGN
            WHEN index_tuple_hdr_bm%maxalign = 0 THEN maxalign
            ELSE index_tuple_hdr_bm%maxalign
          END
        + nulldatawidth + maxalign - CASE -- Add padding to the data to align on MAXALIGN
            WHEN nulldatawidth = 0 THEN 0
            WHEN nulldatawidth::integer%maxalign = 0 THEN maxalign
            ELSE nulldatawidth::integer%maxalign
          END
      )::numeric AS nulldatahdrwidth, pagehdr, pageopqdata, is_na
      -- , index_tuple_hdr_bm, nulldatawidth -- (DEBUG INFO)
    FROM (
      SELECT
        i.nspname, i.tblname, i.idxname, i.reltuples, i.relpages, i.relam, a.attrelid AS table_oid,
        current_setting('block_size')::numeric AS bs, fillfactor,
        CASE -- MAXALIGN: 4 on 32bits, 8 on 64bits (and mingw32 ?)
          WHEN version() ~ 'mingw32' OR version() ~ '64-bit|x86_64|ppc64|ia64|amd64' THEN 8
          ELSE 4
        END AS maxalign,
        /* per page header, fixed size: 20 for 7.X, 24 for others */
        24 AS pagehdr,
        /* per page btree opaque data */
        16 AS pageopqdata,
        /* per tuple header: add IndexAttributeBitMapData if some cols are null-able */
        CASE WHEN max(coalesce(s.null_frac,0)) = 0
          THEN 2 -- IndexTupleData size
          ELSE 2 + (( 32 + 8 - 1 ) / 8) 
          -- IndexTupleData size + IndexAttributeBitMapData size ( max num filed per index + 8 - 1 /8)
        END AS index_tuple_hdr_bm,
        /* data len: we remove null values save space using it fractionnal part from stats */
        sum( (1-coalesce(s.null_frac, 0)) * coalesce(s.avg_width, 1024)) AS nulldatawidth,
        max( CASE WHEN a.atttypid = 'pg_catalog.name'::regtype THEN 1 ELSE 0 END ) > 0 AS is_na
      FROM pg_attribute AS a
        JOIN (
          SELECT nspname, tbl.relname AS tblname, idx.relname AS idxname, 
            idx.reltuples, idx.relpages, idx.relam,
            indrelid, indexrelid, indkey::smallint[] AS attnum,
            coalesce(substring(
              array_to_string(idx.reloptions, ' ')
               from 'fillfactor=([0-9]+)')::smallint, 90) AS fillfactor
          FROM pg_index
            JOIN pg_class idx ON idx.oid=pg_index.indexrelid
            JOIN pg_class tbl ON tbl.oid=pg_index.indrelid
            JOIN pg_namespace ON pg_namespace.oid = idx.relnamespace
          WHERE pg_index.indisvalid AND tbl.relkind = 'r' AND idx.relpages > 0
        ) AS i ON a.attrelid = i.indexrelid
        JOIN pg_stats AS s ON s.schemaname = i.nspname
          AND ((s.tablename = i.tblname AND s.attname = pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE)) 
          -- stats from tbl
          OR  (s.tablename = i.idxname AND s.attname = a.attname))
          -- stats from functionnal cols
        JOIN pg_type AS t ON a.atttypid = t.oid
      WHERE a.attnum > 0
      GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9
    ) AS s1
  ) AS s2
    JOIN pg_am am ON s2.relam = am.oid WHERE am.amname = 'btree'
) AS sub
-- WHERE NOT is_na
ORDER BY 2,3,4;

3、查找符合autovacuum条件的表

若要查找符合自动清除条件的表,请运行以下查询。

--This query shows tables that need vacuuming and are eligible candidates.
--The following query lists all tables that are due to be processed by autovacuum. 
-- During normal operation, this query should return very little.
WITH  vbt AS (SELECT setting AS autovacuum_vacuum_threshold 
              FROM sys_settings WHERE name = 'autovacuum_vacuum_threshold')
    , vsf AS (SELECT setting AS autovacuum_vacuum_scale_factor 
              FROM sys_settings WHERE name = 'autovacuum_vacuum_scale_factor')
    , fma AS (SELECT setting AS autovacuum_freeze_max_age 
              FROM sys_settings WHERE name = 'autovacuum_freeze_max_age')
    , sto AS (SELECT opt_oid, split_part(setting, '=', 1) as param, 
                split_part(setting, '=', 2) as value 
              FROM (SELECT oid opt_oid, unnest(reloptions) setting FROM sys_class) opt)
SELECT
    '"'||ns.nspname||'"."'||c.relname||'"' as relation
    , sys_size_pretty(sys_table_size(c.oid)) as table_size
    , age(relfrozenxid) as xid_age
    , coalesce(cfma.value::float, autovacuum_freeze_max_age::float) autovacuum_freeze_max_age
    , (coalesce(cvbt.value::float, autovacuum_vacuum_threshold::float) + 
         coalesce(cvsf.value::float,autovacuum_vacuum_scale_factor::float) * c.reltuples) 
         as autovacuum_vacuum_tuples
    , n_dead_tup as dead_tuples
FROM sys_class c 
JOIN sys_namespace ns ON ns.oid = c.relnamespace
JOIN sys_stat_all_tables stat ON stat.relid = c.oid
JOIN vbt on (1=1) 
JOIN vsf ON (1=1) 
JOIN fma on (1=1)
LEFT JOIN sto cvbt ON cvbt.param = 'autovacuum_vacuum_threshold' AND c.oid = cvbt.opt_oid
LEFT JOIN sto cvsf ON cvsf.param = 'autovacuum_vacuum_scale_factor' AND c.oid = cvsf.opt_oid
LEFT JOIN sto cfma ON cfma.param = 'autovacuum_freeze_max_age' AND c.oid = cfma.opt_oid
WHERE c.relkind = 'r' 
AND nspname <> 'sys_catalog'
AND (
    age(relfrozenxid) >= coalesce(cfma.value::float, autovacuum_freeze_max_age::float)
    or
    coalesce(cvbt.value::float, autovacuum_vacuum_threshold::float) + 
      coalesce(cvsf.value::float,autovacuum_vacuum_scale_factor::float) * c.reltuples <= n_dead_tup
    -- or 1 = 1
) ORDER BY age(relfrozenxid) DESC;

解决大量连接

当发现DatabaseConnections出现峰值。这表示数据库的连接数量增加。

建议采用以下方法:

限制应用程序可以对每个实例打开的连接数。考虑使用连接池功能管理数据库连接,请设置合理的连接数量,设置基于实例中CPU可以有效并行化的数量。

对于不使用的空闲连接要及时释放,避免消耗过多服务器内存资源。

posted @ 2024-07-26 11:04  KINGBASE研究院  阅读(33)  评论(0编辑  收藏  举报