云数据库 GaussDB 故障案例库

举报
GaussDB 数据库 发表于 2025/12/05 11:20:23 2025/12/05
【摘要】 目 录1 性能类故障 2 资源类故障 2.1 xlog堆积故障处理 2.2 动态内存高故障处理 2.3 表空间膨胀处理 2.4 长事务分析2.5 锁问题 3 使用类故障3.1 JDBC问题处理4 资源负载类问题4.1 内存过载5 逻辑解码常见问题5.1 逻辑解码连接失败5.2 逻辑解码内存故障 5.3 逻辑解码复制槽异常6 分区...

1、性能类故障

2、资源类故障

2.1 xlog堆积故障处理

    • 故障现象

    单个或者多个DN xlog目录持续增大,长时间不回收,导致磁盘空间不足,当达到磁盘使用阈值时,DN会被自动设置为只读,业务不可用。

    • 故障原因

    可能原因有以下:

    l   xlog相关参数配置不合理。

    l   delay_xlog_recycle文件残留。

    l   逻辑复制槽占用。

    l   备份失败。

    l   业务数据持续写入,xlog回收速率无法追赶上生成速率。

    l   降副本后,被删除的副本还在复制槽列表中。

    • 处理方法

                                    步骤 1     登录DBS运维管理平台,

                                    步骤 2     在实例管理-实例列表界面根据实例ID搜索到对应的实例,点击更多-实例监控,查看xlog数量监控指标。

    212.png

    如上图,展示全部节点的xlog数量监控指标。

                                    步骤 3     登录故障的DN节点,进入到DN的数据目录,DN数据目录可通过查询集群状态获取,查看xlog个数。

    cm_ctl query -Cvd

    2131.png

    如上图,/opt/gaussdb/engine/data/dn_6001dn_6001的数据目录。

    ll /opt/gaussdb/engine/data/dn_6001/pg_xlog/ |wc -l

    2132.png

    说明

    1.     公有云或者HCS场景进入数据目录需要进入沙箱或者数据目录前加/var/chroot

    2.     /opt/gaussdb/engine/data/dn_6001/pg_xlog为示例DN数据目录,需根据实际情况修改。

                                    步骤 4     gsql连接DN,查看集群当前xlog相关的参数。

    gsql -d postgres -p 40000 -U user -W password -r -m
    
    show checkpoint_segments;
    
    show wal_keep_segments;
    
    show max_size_for_xlog_prune;
    
    show enable_xlog_prune;

    214.png

    参数说明:

    checkpoint_segments:设置checkpoint_timeout周期内所保留的最少WAL日志段文件数量。每个日志文件大小为16MB

    wal_keep_segments:保留事务日志文件的最小数目。

    当日志量达到一定量时,日志才会触发回收,该值的计算方式是:wal_keep_segments +checkpoint_segments * 2 + 1 ,假设checkpoint_segments设置128wal_keep_segments设置128,日志量就是 (128 + 128 * 2 + 1)=385个。若该值设置很大,可能会导致磁盘占用过多。如果查询xlog数量不足该阈值且配置阈值过大,考虑调低以上参数。

    max_size_for_xlog_prune:在enable_xlog_prune打开时生效,机制如下:

    l   若有备机断连,当主机历史日志数量大于该参数值,会强制回收。

    l   如果有任何一个备机正在build,那么该参数不会生效,主机日志会全量保留,防止build操作由于日志回收重复失败。

    enable_xlog_prune:设置在任一备机断联时,主机是否根据xlog日志的大小超过参数max_size_for_xlog_prune的值而回收日志。

    说明

    1.     分布式gsql连接DN,需要首先获取当前DN的端口号,集中式DN默认端口号为8000

    2.     DN的端口号可通过cm_ctl query -Cvdp命令查询获取。

    3.     如果连接备DN,需要gsql命令后需加“-m”参数。

    l   若参数设置不合理,需重新设置参数,若需重新设置参数,联系华为技术支持。

    l   若以上参数设置合理,继续执行以下步骤。

                                    步骤 5     查看DN数据目录下是否有delay_xlog_recycle文件。

    l   V500R001C10版本,当DN数据目录下有delay_xlog_recycle文件时,说明是备份失败导致,下一次备份成功,xlog回收会正常触发。

    l   若非以上原因,继续执行以下步骤;

                                    步骤 6     确认是否存在逻辑复制槽占用。

    gsql -d postgres -p 40000 -U user -W password -r -m
    
    select * from pg_get_replication_slots();
    
    grep 'attempting to remove WAL segments older than log file' postgresql-yyyy-mm-dd_******.log

    2161.png

    说明:

    分布式gsql连接DN,需要首先获取当前DN的端口号,集中式DN默认端口号为8000

    如上图,逻辑复制槽replicationrestart_lsn4/8AE62280

    2162.png

    如上图,DN日志中的日志点一直为000000010000000400000088,未推进,这与逻辑复制槽的restart_lsn刚好一致,说明xlog不回收是由与逻辑复制槽阻塞,参考处理步骤1

    l   若有逻辑复制槽,确认对应的LSN是否为pg_log日志中attempting to remove WAL segments newer than log file对应的xlog编号,若对应,则说明为逻辑复制槽占用导致,需要清理逻辑复制槽解决,若不对应,继续向下执行。

    l   若非以上原因,继续执行。

                                    步骤 7     确认是否备份失败导致。

    确认gs_roach占用的槽位的LSN是否为pg_log日志中attempting to remove WAL segments newer than log file对应的xlog编号。

    select * from pg_get_replication_slots();
    
    grep 'attempting to remove WAL segments older than log file' postgresql-yyyy-mm-dd_******.log

    2171.png

    2172.png

    如上图中,gs_roach_full表示正在全备,gs_roach_inc表示正在增备。

    2173.png

    如上图中,000000010000000500000081中的第二个8位,00000005于逻辑复制槽(/8800E250)中的5对应,如果备份任务流已失败,则xlog堆积是由于备份失败导致。

    l   若是,则说明是备份失败导致,待下次备份正常触发则可以正常回收。

    l   若不涉及,继续向下执行。

                                    步骤 8     确认是否进行过降副本操作。

    如果进行过降副本操作,登录xlog堆积的DN节点查询,确认删除的副本是否还在复制槽列表中,如果还在,说明降副本后复制槽没有删除,需要清理残留的复制槽,参考处理步骤1

    gsql -d postgres -p 40000 -U user -W password -r -m
    
    select * from pg_get_replication_slots();

    218.png

    若非以上都不涉及,联系华为技术支持。

    说明:

    分布式gsql连接DN,需要首先获取当前DN的端口号,集中式DN默认端口号为8000

                                    步骤 9     当主DN存在导入数据等大批量业务时,备DN回放速度赶不上主DN xlog产生速度时,xlog短时间内也不会回收。

    cm_ctl query -rv

    219.png

                                 步骤 10     删除逻辑复制槽。

    select * from pg_drop_replication_slot('replication');

    须知:删除逻辑复制槽为高危操作,执行前需联系华为技术支持确认。

                                 步骤 11     上述场景不包含,联系华为技术支持。

    ----结束

    • 快速恢复

    1、当前xlog堆积,未达到磁盘使用阈值,业务无感知,满足快速恢复。

    2、当xlog堆积,造成DN只读,快速恢复参考DN故障-状态ReadOnly

    2.2 动态内存高故障处理

    • 故障现象

    单个或者多个节点出现动态内存使用率超过阈值,动态内存快速上涨等现象,或者业务执行SQL报错:ERROR:memory is temporarily unavailable

    • 故障原因

    可能原因有以下:

    l   会话数上涨。

    l   SQL计划缓存增多。

    l   内存泄漏。

    l   参数设置不合理。

    • 处理方法

                                    步骤 1     登录DN节点,分别查看DN内存使用情况。

    gsql -d postgres -p 40000 -U user -W password -r
    
    select * from pg_total_memory_detail;

    221.png

    如上图,为当前DN的内存使用情况.

    说明:

    1.     分布式gsql连接DN,需要首先获取当前DN的端口号,集中式DN默认端口号为8000

    2.     DN的端口号可通过cm_ctl query -Cvdp命令查询获取。

    3.     如果连接备DN,需要gsql加“-m”参数。

                                    步骤 2     当同节点的“dynamic_used_memory”大于“max_dynamic_memory”时会报内存不足,参考处理步骤1

                                    步骤 3     当同节点的“dynamic_used_memory”小于“max_dynamic_memory”但“dynamic_peak_memory”大于“max_dynamic_memory”时,说明曾经出现过内存不足的情况,参考处理步骤1

                                    步骤 4     若同节点的“dynamic_used_shrctx/max_dynamic_memory”大于80%时,请登录该节点执行以下命令查询“pg_shared_memory_detail”视图,查看使用内存较多的MemoryContext,参考处理步骤2

    分布式:

    select * from pg_shared_memory_detail order by usedsize desc;

    集中式:

    select * from gs_shared_memory_detail order by usedsize desc;

    224.png

    说明:

    1.     分布式gsql连接DN,需要首先获取当前DN的端口号,集中式DN默认端口号为8000

    2.     DN的端口号可通过cm_ctl query -Cvdp命令查询获取。

    3.     如果连接备DN,需要gsql加“-m”参数。

                                    步骤 5     若同节点的“dynamic_used_shrctx/max_dynamic_memory”小于40%时,请登录该节点执行以下命令查询“pv_session_memory_detail”视图,查看使用内存较多的MemoryContext,参考处理步骤2

    分布式:

    select * from pv_session_memory_detail;

    集中式:

    select * from gs_session_memory_detail;

    225.png

    说明

    1.     分布式gsql连接DN,需要首先获取当前DN的端口号,集中式DN默认端口号为8000

    2.     DN的端口号可通过cm_ctl query -Cvdp命令查询获取。

    3.     如果连接备DN,需要gsql加“-m”参数。

                                    步骤 6     若同节点的“other_used_memory/process_used_memory”大于50%时,请联系华为技术支持。

                                    步骤 7     若同节点的“dynamic_used_memory/max_dynamic_memory”大于80%,请登录该节点执行以下命令查询视图,参考处理步骤2

    分布式:

    l   查看具体占用内存较高的内存上下文

    select contextname, sum(totalsize)/1024/1024 as total, sum(usedsize)/1024/1024,count(*) as used from pv_session_memory_detail group by 1 order by 2 desc limit 20;

    l   查询占用动态内存较高的会话

    select sessid, sum(totalsize)/1024/1024 as "totalsize MB", count(1) as count from pv_session_memory_context group by sessid order by 2 desc limit 10;

    集中式:

    l   查看具体占用内存较高的内存上下文

    select contextname, sum(totalsize)/1024/1024 as total, sum(usedsize)/1024/1024,count(*) as used from gs_session_memory_detail group by 1 order by 2 desc limit 20;

    l   查询占用动态内存较高的会话

    select sessid, sum(totalsize)/1024/1024 as "totalsize MB", count(1) as count from gs_session_memory_context group by sessid order by 2 desc limit 10;

    说明:

    1.     分布式gsql连接DN,需要首先获取当前DN的端口号,集中式DN默认端口号为8000

    2.     DN的端口号可通过cm_ctl query -Cvdp命令查询获取。

    3.     如果连接备DN,需要gsql加“-m”参数。

    4.     sessid说明:

           关闭线程池(enable_thread_pool = off)时该字段表示线程启动时间+session标识(字符串信息为timestamp.sessionid)。

           开启线程池(enable_thread_pool = on)时,内存上下文是线程级别的,则对应的该字段表示线程启动时间+线程标识(字符串信息为timestamp.threadid)。

                                    步骤 8     根据步骤7sessid查询获取pidsessionid

    l   若关闭线程池(enable_thread_pool = off),则执行以下SQL

    select pid,sessionid from pg_stat_activity where sessionid='$sessionid';

    l   若开启线程池(enable_thread_pool = on),则执行以下SQL

    select pid,sessionid from pg_stat_activity where pid='$sessionid';

    说明:

    $sessionid步骤7获取到的sessid后半部分,即“.“之后的部分。

    228.png

    如上图,sessionid111630

                                    步骤 9     观察内存占用较大的上下文,若会话数不变,动态内存依然快速上涨,则可能为内存泄露,参考处理步骤3

                                 步骤 10     获取CN/DNmax_process_memoryshared_buffers参数值,若shared_buffers值过大,根据标准配置,重新设置该参数值,参考《开发指南》。

    show max_process_memory;
    
    show shared_buffers;

    说明:

    1.     分布式gsql连接DN,需要首先获取当前DN的端口号,集中式DN默认端口号为8000

    2.     DN的端口号可通过cm_ctl query -Cvdp命令查询获取。

    3.     如果连接备DN,需要gsql加“-m”参数。

    须知:以上参数设置,需要重启集群生效,需谨慎评估。

                                 步骤 11     CachedPlan上下文内存占用较多(作用:SQL计划缓存,下一次查询时可以减少SQL执行计划的生成时间),需减少会话数、改写业务、减少SQL模板数以及在JDBC连接串配置较小的preparedStatementCacheQueries值,减少每个会话缓存的语句数量。

    l   SessionCacheMemoryContext上下文内存占用较多(作用:会话元数据缓存),需减少会话数、在enable_global_syscacheoff的情况下,可以调小local_syscache_threshold,减少单个会话占用的缓存大小。

    l   TwoPhrase Cleaner上下文内存占用较多(作用:该内存是事务两阶段提交时,临时表自动清理所占用的内存),需修改gs_clean_timeout参数为0,禁止自动清理临时表,使TwoPhrase Cleaner不再增长。

    须知:该操作为高危操作,执行前需联系华为技术支持确认。

                                 步骤 12     请执行以下语句保留当前内存上下文上的内存申请详细信息(使用时根据实际内存上下文替换参数值),用于后续定位。

    select gs_get_session_memctx_detail('$contextname');

    说明:

    $contextname步骤7获取到的contextname列。

    执行pg_terminate_session结束相应会话。

    select pg_terminate_session('$pid', '$sessionid');

    说明:

    $pid$sessionid步骤8获取到的pidsessionid

    须知:pg_terminate_session函数结束会话为高危操作,执行前需联系华为技术支持确认。

    kill会话之后仍然无法恢复,则重启DN进程。

    kill -9 pid

    说明:

    pid为当前节点dn进程的pid,可通过ps -ux|grep dn|grep -v grep查询获取。

    须知:kill操作为高危操作,执行前需联系华为技术支持确认。

                                 步骤 13     若以上均不涉及,请联系华为技术支持。

    ----结束

    2.3表空间膨胀处理

    • 故障现象

    base目录占用磁盘空间大,监控页面dbsize增长快,不符合预期。

    • 故障原因

    可能原因有以下:

    长事务阻止。

    • 处理方法

                                    步骤 1     登录CN节点,并通过gsql连接数据库。

    gsql -d postgres -p 40000 -U user -W password -r

    主备版登录主DN节点。

                                    步骤 2     查看数据库大小。

    select datname, pg_database_size(datname) as size from pg_database;

    232.png

                                    步骤 3     确认业务是否执行批量导数据等业务,如在批量导数据,表空间短暂膨胀属于正常现象,若不涉及继续步骤4

                                    步骤 4     查看数据库中脏页率高的表。

    select relname,schemaname,n_live_tup,n_dead_tup,round(n_dead_tup * 100 / (n_live_tup + n_dead_tup+0.0001),2) as dead_tup_ratio,last_autovacuum, last_vacuum,vacuum_count, autovacuum_count from pg_stat_all_tables where dead_tup_ratio > 0 order by dead_tup_ratio desc, last_autoanalyze limit 20;

    234.png

                                    步骤 5     确认是否存在长事务。

    分布式:

    execute direct on all 'select * from pg_running_xacts where xmin::text::bigint <>0 order by xmin::text::bigint asc limit 10;';

    集中式:

    select * from pg_running_xacts where xmin::text::bigint <>0 order by xmin::text::bigint asc limit 10;

    235.png

                                    步骤 6     根据查询结果中的node信息,查询对应各节点的OldestXmin日志。

    cd $GAUSSLOG/pg_log/{node}
    
    grep OldestXmin postgresql-2022-12-02_000000.log

    236.png

    l   如查询出Oldestxmin与步骤5中的xmin一致,且一直未变化,则表明当前pid存在SQL,继续步骤7

    l   若不涉及,联系华为技术支持。

                                    步骤 7     查询当前正在执行的SQL

    分布式:

    execute direct on all 'select * from pg_stat_activity where pid in(select pid from pg_running_xacts where xmin::text::bigint <>0 order by xmin::text::bigint asc limit 10);';

    集中式:

    select * from pg_stat_activity where pid in(select pid from pg_running_xacts where xmin::text::bigint <>0 order by xmin::text::bigint asc limit 10);

    如查询出该语句,则继续步骤8,若未查询出结果,则联系华为技术支持。

                                    步骤 8     强制结束该语句。

    分布式:

    execute direct on all 'select pg_terminate_session(pid,sessionid),pid,sessionid from pg_stat_activity where pid in(select pid from pg_running_xacts where xmin::text::bigint <>0 order by xmin::text::bigint asc limit 10);';

    集中式:

    select pg_terminate_session(pid,sessionid),pid,sessionid from pg_stat_activity where pid in(select pid from pg_running_xacts where xmin::text::bigint <>0 order by xmin::text::bigint asc limit 10);

    须知:l  pg_terminate_session为高危操作,执行前需联系业务及华为技术支持确认。

                                    步骤 9     若以上均不涉及,请联系华为技术支持。

    ----结束

    2.4 长事务分析

    • 故障现象

    长事务在数据库的表现是session持续时间长,期间可能伴随cpu、内存升高,严重可导致数据库整体响应缓慢,业务无法正常运行。

    • 故障原因

    可能原因有以下:

    l   大量的锁竞争。

    l   执行了比较耗时的SQL

    • 处理方法

                                    步骤 1     登录CN节点,gsql连接数据库。

    gsql -d postgres -p 8000 -U user -W password -r

    说明:

    集中式实例登录主DN节点。

                                    步骤 2     查询正在运行的SQL语句详细信息,runtime表示SQL语句运行的时间,超过阈值时间或者当前语句执行时间超过预期,即可认为是长事务,以下统称长事务。

    分布式:

    execute direct on all 'select current_timestamp - query_start as runtime,datname,usename,sessionid,substr(query,0,100) from pg_stat_activity where state != ''idle'' and datname in(''$database'') and usename in (''$user'') and extract(epoch from current_timestamp-xact_start)/60 > 1 order by 1 desc;';

    集中式:

    select current_timestamp - query_start as runtime,datname,usename,sessionid,substr(query,0,100) from pg_stat_activity where state != 'idle' and datname in('$database') and usename in ('$user') and extract(epoch from current_timestamp-xact_start)/60 > 1 order by 1 desc;

    242.png

    如上图,查询到sessionid=52246的长事务。

    说明:

    1.     $user$database需根据实际情况修改。

    2.     extract(epoch from current_timestamp-xact_start)/60 > 1表示查询执行时间大于1minSQL,可根据实际情况修改。

                                    步骤 3     查看数据库中正在运行的等待事件。

    分布式:

    execute direct on all 'select * from pg_thread_wait_status where sessionid in(select sessionid from pg_stat_activity where state != ''idle'' and datname in(''$database'') and usename in (''$user'') and extract(epoch from current_timestamp-xact_start)/60 > 1);';

    集中式:

    select * from pg_thread_wait_status where sessionid in(select sessionid from pg_stat_activity where state != 'idle' and datname in('$database') and usename in ('$user') and extract(epoch from current_timestamp-xact_start)/60 > 1);

    243.png

    如上图,查询到sessionid=52246的长事务的等待事件。

    说明:

    1.     $user$database需根据实际情况修改,建议和步骤2中的条件保持一致。

    2.     extract(epoch from current_timestamp-xact_start)/60 > 1表示查询执行时间大于1minSQL,可根据实际情况修改,建议和步骤2中的条件保持一致。

                                    步骤 4     查询长事务被阻塞原因。

    分布式:

    execute direct on all 'select * from pg_thread_wait_status where sessionid in(select block_sessionid from pg_thread_wait_status where sessionid in(select sessionid from pg_stat_activity where state != ''idle'' and datname in(''$database'') and usename in (''$user'') and extract(epoch from current_timestamp-xact_start)/60 > 1));';

    集中式:

    select * from pg_thread_wait_status where sessionid in(select block_sessionid from pg_thread_wait_status where sessionid in(select sessionid from pg_stat_activity where state != 'idle' and datname in('$database') and usename in ('$user') and extract(epoch from current_timestamp-xact_start)/60 > 1));

    244.png

    如上图,sessionid=52241的会话阻塞了步骤2sessionid=52246的长事务。

    说明:

    1.     $user$database需根据实际情况修改,建议和步骤2中的条件保持一致。

    2.     extract(epoch from current_timestamp-xact_start)/60 > 1表示查询执行时间大于1minSQL,可根据实际情况修改,建议和步骤2中的条件保持一致。

    l   若未查询到数据,则表明该长事务不是被阻塞其他线程阻塞,可选择结束长事务或者优化SQL

    a、结束长事务,可执行以下SQL

    分布式:

    execute direct on all 'select pg_terminate_session(pid,sessionid) pid,sessionid from pg_stat_activity where state != ''idle'' and datname in(''$database'') and usename in (''$user'') and extract(epoch from current_timestamp-xact_start)/60 > 1 order by 1 desc;';

    集中式:

    select pg_terminate_session(pid,sessionid) pid,sessionid from pg_stat_activity where state != 'idle' and datname in('$database') and usename in ('$user') and extract(epoch from current_timestamp-xact_start)/60 > 1 order by 1 desc;

    说明:

    1.     $user$database需根据实际情况修改,建议和步骤2中的条件保持一致。

    2.     extract(epoch from current_timestamp-xact_start)/60 > 1表示查询执行时间大于1minSQL,可根据实际情况修改,建议和步骤2中的条件保持一致。

    须知:强制结束SQL语句为风险操作,执行前需与客户及华为技术确认。

    b、优化SQL,可查询执行计划后参照单SQL性能慢--计划分析优化该语句。

    explain $query;

    说明:

    $query为步骤2中获取的语句。

    l   若查询到数据,则表明长事务被其他线程阻塞,继续步骤5

                                    步骤 5     获取阻塞长事务的SQL语句。

    分布式:

    execute direct on all 'select pid,sessionid,substr(query,0,100) from pg_stat_activity where sessionid in(select sessionid from pg_thread_wait_status where sessionid in(select block_sessionid from pg_thread_wait_status where sessionid in(select sessionid from pg_stat_activity where state != ''idle'' and datname in(''$database'') and usename in (''$user'') and extract(epoch from current_timestamp-xact_start)/60 > 1)));';

    集中式:

    select pid,sessionid,substr(query,0,100) from pg_stat_activity where sessionid in(select sessionid from pg_thread_wait_status where sessionid in(select block_sessionid from pg_thread_wait_status where sessionid in(select sessionid from pg_stat_activity where state != 'idle' and datname in('$database') and usename in ('$user') and extract(epoch from current_timestamp-xact_start)/60 > 1)));

    2451.png

    如上图,查询出阻塞长事务的SQL语句,其sessionid=52241

    l   若数据库整体性能劣化严重,需要紧急处理本步骤中的SQL语句,即强制结束该SQL语句:

    分布式:

    execute direct on all 'select pg_terminate_session(pid,sessionid) pid,sessionid from pg_stat_activity where sessionid in(select sessionid from pg_thread_wait_status where sessionid in(select block_sessionid from pg_thread_wait_status where sessionid in(select sessionid from pg_stat_activity where state != ''idle'' and datname in(''$database'') and usename in (''$user'') and extract(epoch from current_timestamp-xact_start)/60 > 1)));';

    集中式:

    select pg_terminate_session(pid,sessionid) pid,sessionid from pg_stat_activity where sessionid in(select sessionid from pg_thread_wait_status where sessionid in(select block_sessionid from pg_thread_wait_status where sessionid in(select sessionid from pg_stat_activity where state != 'idle' and datname in('$database') and usename in ('$user') and extract(epoch from current_timestamp-xact_start)/60 > 1)));

    2452.png

    如上图,pid=t,表示结束会话成功。

    说明:

    1.     $user$database需根据实际情况修改,建议和步骤2中的条件保持一致。

    2.     extract(epoch from current_timestamp-xact_start)/60 > 1表示查询执行时间大于1minSQL,可根据实际情况修改,建议和步骤2中的条件保持一致。

    须知:强制结束SQL语句为风险操作,执行前需与客户及华为技术确认。

    l   若当前不紧急或者性能劣化不严重,可先执行步骤6

                                    步骤 6     查看步骤5SQL语句的执行计划,参照单SQL性能慢--计划分析优化该语句。

    explain $query;

    说明:

    $query步骤5中获取的语句。

                                    步骤 7     若以上均不涉及,请联系华为技术支持。

    ----结束

    2.5 锁问题

    • 故障现象

    出现会话等锁、等锁超时、死锁、并发更新性能下降等。

    • 故障原因

    可能原因有以下:

    l   行锁等待,业务逻辑问题,对数据的并发处理顺序有问题,导致死锁、等锁超时或者并发更新同一行。

    l   页面锁等待,常见由于数据库内核资源争抢导致。

    l   IO锁等待,常见由于数据在内存和磁盘之间置换存在并发导致。

    • 处理方法

    l   死锁或所等待超时分析

                                    步骤 1     登录CN节点,进入CN日志目录cd $GAUSSLOG/pg_log/cn_xxxx,搜索死锁或锁等待超时关键词。

                                    步骤 2     检查对应报错时间段是否存在死锁:

    grep "deadlock detected" postgresql-******.log

    说明:

    若日志被压缩,则执行zgrep "deadlock detected" postgresql-******.log

                                    步骤 3     检查对应报错时间段是否存在锁等待超时:

    grep "Lock wait timeout" postgresql-******.log

    说明:

    若日志被压缩,则执行zgrep "Lock wait timeout" postgresql-******.log

                                    步骤 4     找到死锁或等锁超时触发的语句,若是死锁,则需要从语句和事务逻辑分析,联系业务排查是否有不合理并发;若是等锁超时,需要结合gs_asp分析,历史会话中对应的持锁和等锁语句的行为,可以参考性能劣化问题分析

    ----结束

    l   性能劣化问题分析

    说明:

    本步骤主要分析锁超时导致的整体性能劣化问题。

                                    步骤 1     gsql连接数据库。

    gsql -d postgres -p port -U user -W password -r

    说明:

    分布式需要连接CN节点,集中式需要登录主DN节点。

                                    步骤 2     查看运行时间长的SQL语句。

    分布式:

    execute direct on all 'select current_timestamp - query_start as runtime,datname,usename,sessionid,substr(query,0,100) from pg_stat_activity where state != ''idle'' and datname in(''$database'') and usename in (''$user'') and extract(epoch from current_timestamp-xact_start)/60 > 1 order by 1 desc;';

    集中式:

    select current_timestamp - query_start as runtime,datname,usename,sessionid,substr(query,0,100) from pg_stat_activity where state != 'idle' and datname in('$database') and usename in ('$user') and extract(epoch from current_timestamp-xact_start)/60 > 1 order by 1 desc;

    说明:

    1.     $user$database需根据实际情况修改。

    2.     extract(epoch from current_timestamp-xact_start)/60 > 1表示查询执行时间大于1minSQL,可根据实际情况修改。

                                    步骤 3     查询业务是否被锁阻塞,以下为查询阻塞业务的SQL语句,即锁持有者。

    l   若是因为SQL语句慢导致持有锁而阻塞业务,则需要优化该SQL语句,参照单SQL性能慢-计划分析。

    l   若是因为业务逻辑不合理,导致锁问题,参照步骤5

    分布式:

    execute direct on all 'select substr(w.query,0,100),l.granted,w.pid,w.sessionid,w.usename from pg_stat_activity w join pg_locks l on w.pid = l.pid where state != ''idle'' and w.datname in(''$database'') and w.usename in (''$user'') and l.granted=true;';

    集中式:

    select substr(w.query,0,100),l.granted,w.pid,w.sessionid,w.usename from pg_stat_activity w join pg_locks l on w.pid = l.pid where state != 'idle' and w.datname in('$database') and w.usename in ('$user') and l.granted=true;

    说明:

    $user$database需根据实际情况修改,建议和步骤2中的条件保持一致。

                                    步骤 4     若要结束该阻塞语句,执行以下SQL

    分布式:

    execute direct on all 'select pg_terminate_session(w.pid,w.sessionid),w.pid,w.sessionid from pg_stat_activity w join pg_locks l on w.pid = l.pid where state != ''idle'' and w.datname in(''$database'') and w.usename in (''$user'') and l.granted=true;';

    集中式:

    select pg_terminate_session(w.pid,w.sessionid),,w.pid,w.sessionid from pg_stat_activity w join pg_locks l on w.pid = l.pid where state != 'idle' and w.datname in('$database') and w.usename in ('$user') and l.granted=true;

    说明:

    $user$database需根据实际情况修改,建议和步骤2中的条件保持一致。

    须知:结束会话语句为高危操作,执行前需联系业务及华为技术支持确认。

                                    步骤 5     紧急处理后可继续分析,若整体业务逻辑不合理,需业务修改应用程序逻辑,同时优化锁相关参数,涉及参数如下:

    1deadlock_timeout

    参数说明:设置死锁超时检测时间,以毫秒为单位。当申请的锁超过设定值时,系统会检查是否产生了死锁。

    默认值:1s

    2lockwait_timeout

    参数说明:控制单个锁的最长等待时间。当申请的锁等待时间超过设定值时,系统会报错。

    默认值:20min

    3update_lockwait_timeout

    参数说明:允许并发更新参数开启情况下,该参数控制并发更新同一行时单个锁的最长等待时间。当申请的锁等待时间超过设定值时,系统会报错。

    默认值:1200002min

    4max_locks_per_transaction

    参数说明:控制每个事务能够得到的平均的对象锁的数量。

    l   共享的锁表的大小是以假设任意时刻最多只有max_locks_per_transaction*(max_connections+max_prepared_transactions) 个独立的对象需要被锁住为基础进行计算的。不超过设定数量的多个对象可以在任一时刻同时被锁定。当在一个事务里面修改很多不同的表时,可能需要提高这个默认数值。只能在数据库启动的时候设置。

    l   增大这个参数可能导致GaussDB Kernel请求更多的System V共享内存,有可能超过操作系统的缺省配置。

    l   当运行备机时,请将此参数设置不小于主机上的值,否则,在备机上查询操作不会被允许。

    默认值:256

    5max_pred_locks_per_transaction

    参数说明:控制每个事务允许断定锁的最大数量,是一个平均值。

    l   共享的断定锁表的大小是以假设任意时刻最多只有max_pred_locks_per_transaction*(max_connections+max_prepared_transactions) 个独立的对象需要被锁住为基础进行计算的。不超过设定数量的多个对象可以在任一时刻同时被锁定。当在一个事务里面修改很多不同的表时,可能需要提高这个默认数值。只能在服务器启动的时候设置。

    l   增大这个参数可能导致GaussDB Kernel请求更多的System V共享内存,有可能超过操作系统的缺省配置。

    默认值:64

    须知:修改锁相关参数为高危操作,修改前需联系华为技术支持确认。

    ----结束

    3.使用类故障

    3.1 JDBC问题处理

    • 故障现象

    JDBC连接数据库或者执行业务语句报错,业务成功率下降或者业务持续失败。

    • 故障原因

    可能原因有以下:

    1.   应用程序到数据库之间网络通信问题。

    2.  JDBC配置问题。

    3.   业务逻辑问题。

    4.   数据库问题。

    • 处理方法

    Read timed out问题

                                    步骤 1     查看应用报错日志。

    如报错为:An I/O error occured while sending to the backend.detail:Read timed out,如下图,则原因是连接超时。

    311.png

                                    步骤 2     查看JDBC url配置,查看url中是否配置socketTimeout 或者connectTimeout参数。

    l   当设置了connectTimeout时,JDBC创建tcp套接字进行建连时,等待的时间超过了connectTimeout,会抛出异常,这种情况,一般是网络问题或者内核通讯问题导致,需排查故障时的网络状况以及线程池使用情况。

    实时查询线程池使用情况:

    select node_name,session_info from dbe_perf.global_threadpool_status order by 1;

    说明:

    历史线程池使用情况,可通过DBS运维管理平台 > 实例监控查看。

    l   当设置了socketTimeout时,JDBC读取报文,调用SocketInputStream.read()函数,该函数会调用函数socketRead阻塞等待内核返还报文,当阻塞时间超过socketTimeout时,会抛出异常。

    a.  socketTimeout值设置是否合理。

    b.  排查慢SQL,参照单SQL性能慢-视图分析。

    c.  排查故障时网络状况。

    d.  排查有无锁等待超时等,如下图为锁等待超时。

    cd $GAUSSLOG/pg_log/cn_xxxx

    搜索关键词timeout

    312.png

    ----结束

    EOF Exception问题

                                    步骤 1     查看应用报错日志。

                                    步骤 2     如报错为:An I/O error occured while sending to the backend.detail:EOF Exception,如下图,则原因为连接被异常中断。

    3122.png

                                    步骤 3     排查应用程序到数据库网络是否有异常。

                                    步骤 4     执行如下命令,排查数据库集群状态是否正常。

    cm_ctl query -Cvd

    CN或者DN组件异常,分布式集群参照CN故障处理,集中式集群参照DN故障处理。

                                    步骤 5     如集群状态正常,则排查数据库CN组件否有重启,如有重启,则参照CN组件状态Normal-告警原因分析章节排查,集中式排查DN组件是否有重启,如有重启,参照DN组件状态Normal-告警原因分析章节排查,若未排查到原因,则联系华为技术支持。

    ps ux|grep cn|grep -v grep

    3151.png

    ps ux|grep dn|grep -v grep

    3152.png

    ----结束

    认证配置问题

                                    步骤 1     查看应用程序报错日志,如报错为:no pg_hba.conf entry for host,则表示客户端认证配置错误。

    3153.png

                                    步骤 2     连接CN节点,进入到CN目录,集中式场景需要连接DN节点,且进入到DN目录。

                                    步骤 3     查看pg_hba.conf文件配置,如文件中的内容如下。

    host posgres jim 192.168.0.1/32 sha256

    l   host表示表示这条记录既接受一个普通的TCP/IP套接字连接,也接受一个经过SSL加密的TCP/IP套接字连接。

    l   posgres表示允许客户端连接到posgres库,此处为database名称,如允许连接任意库,则此处为all

    l   jim表示允许客户端连接jim用户,此处为user名称,如允许所有用户连接,此处为all

    l   192.168.0.1/32表示允许IP地址为192.168.0.1/32的客户端连接数据库,此处为IP地址,如允许所有的IP地址连接,此处需设置为0.0.0.0/0

    l   sha256表示认证方式,一般都用默认值sha256

                                    步骤 4     如未配置客户端认证,使用以下命令进行配置。

    1.         分布式:gs_guc reload -Z coordinator -N all -I all -h "host all all 0.0.0.0/0 sha256"

    2.         集中式:gs_guc reload -Z datanode -N all -I all -h "host all all 0.0.0.0/0 sha256"

    说明:

    HCS场景,执行gs_guc命令需要进入沙箱。

                                    步骤 5     如应用报错为:Invalid or unsupported by client SCRAM mechanisms,则表示连接数据库认证方式不支持,可能是使用PostgreSQL原生的驱动。

    3155.png

                                    步骤 6     确认jdbc驱动是否为GaussDB版本配套的驱动。

    官网获取的jdbc驱动解压之后包括:gsjdbc4.jargsjdbc200.jaropengaussjdbc.jar,各驱动说明如下:

    l   gsjdbc4.jar:驱动类名和加载路径与PostgreSQL相同,方便运行于PostgreSQL上的业务进行迁移,但接口的支持情况并不与PostgreSQL完全一致,部分不支持接口需要业务侧进行调整。

    l   gsjdbc200.jar:驱动类名和加载路径与Gauss200相同,方便运行于Gauss200上的业务进行迁移,但接口支持情况并不与Gauss200完全相同,部分不支持接口需要业务侧调整。

    l   opengaussjdbc.jar:主类名为“com.huawei.opengauss.jdbc.Driver”,数据库连接的url前缀为“jdbc:opengauss”,推荐使用此驱动包。如果遇到同一JVM进程内需要同时访问PostgreSQLGaussDB Kernel的场景,请使用此驱动包。

    说明:

    当驱动路径下同时存在PostgreSQLGaussDB官方驱动时,也可能导致此报错,需删除PostgreSQL驱动。

    如确认驱动正确,请联系华为技术支持。

    ----结束

    用户名或者密码错误问题。

                                    步骤 1     查看应用程序报错日志,如报错为:Invalid username/password,login denied,则表示为用户名或者密码配置错误。

    3156.png

                                    步骤 2     可使用管理员用户连接数据库,重置用户密码,或者修改程序中的密码为正确密码。

    alter user usename password "********";

                                    步骤 3     如重试多次,账户被锁定,可使用以下SQL语句解锁。

    alter user username account unlock;

                                    步骤 4     如应用程序报错日志为:The account has been locked,则表示为用户被锁定。

    3157.png

                                    步骤 5     可使用以下SQL语句解锁。

    alter user username account unlock;

    ----结束

    4.资源负载类问题

    4.1 内存过载

    • 故障现象

    l   数据库进程内存占比较高。

       长时间占比较高。

       观察监控平台内存占用的变化曲线,无论当前数据库是否有业务在运行,数据库进程内存占总机器内存的比例长时间处于较高状态,且不下降。

      执行作业期间占比较高。

       数据库进程在没有业务执行时,内存使用持续处于较低的状态,当有业务执行时,内存占用升高,待作业执行结束后,内存又恢复到较低的状态。

    内存上涨不下降。

       数据库进程在执行业务过程中内存呈缓慢上涨趋势,且业务执行完后无下降趋势。

    l   SQL语句报内存不足错误。

    执行SQL语句报内存不足的错误,如下所示:

    gaussdb=# select contextname, sum(totalsize)/1024/1024 sum, sum(freesize)/1024/1024, count(*) count from gs_shared_memory_detail group by contextname order by sum desc limit 10;
    ERROR:  memory is temporarily unavailable
    DETAIL:  Failed on request of size 46 bytes under queryid 281475005884780 in heaptuple.cpp:1934.
    • 故障原因

    如果出现集群内存不足或者长时间内存占用处于较高状态的情况,可能是由以下几种原因造成的。

    l   SQL语句执行过程中临时内存未及时释放时,会导致内存堆积。

    l   并发过高导致内存占用过高

    l   客户端向server端建立的连接数过多,导致server端创建了大量的session,占用大量内存,这种场景下会出现内存出现占用较高的情况。

    l   单条SQL语句内存占用较高

    l   对于部分SQL语句,执行过程中会使用大量的内存,导致内存出现短暂性上涨,如复杂的存储过程语句,执行时内存占用可能达到几十GB大小。

    l   内存缓存过多

    GaussDB引入了jemalloc开源库来管理内存管理,但是jemalloc在面对大量的内存碎片时会存在内存持续缓存不释放的问题,导致数据库进程使用的内存远远超过了实际使用的内存,具体表现为使用内存视图pv_total_memory_detail查询时,other_used_memory占用过大。如下所示。

    gaussdb=# select * from pv_total_memory_detail;
     nodename       |       memorytype        | memorymbytes
    ----------------+-------------------------+--------------
     coordinator1   | max_process_memory      |        81920
     coordinator1   | process_used_memory     |        14567
     coordinator1   | max_dynamic_memory      |        34012
     coordinator1   | dynamic_used_memory     |         1851
     coordinator1   | dynamic_peak_memory     |         3639
     coordinator1   | dynamic_used_shrctx     |          394
     coordinator1   | dynamic_peak_shrctx     |          399
     coordinator1   | max_backend_memory      |          648
     coordinator1   | backend_used_memory     |            1
     coordinator1   | max_shared_memory       |        46747
     coordinator1   | shared_used_memory      |        11618
     coordinator1   | max_cstore_memory       |          512
     coordinator1   | cstore_used_memory      |            0
     coordinator1   | max_sctpcomm_memory     |            0
     coordinator1   | sctpcomm_used_memory    |            0
     coordinator1   | sctpcomm_peak_memory    |            0
     coordinator1   | other_used_memory       |         1013
     coordinator1   | gpu_max_dynamic_memory  |            0
     coordinator1   | gpu_dynamic_used_memory |            0
     coordinator1   | gpu_dynamic_peak_memory |            0
     coordinator1   | pooler_conn_memory      |            0
     coordinator1   | pooler_freeconn_memory  |            0
     coordinator1   | storage_compress_memory |            0 
     coordinator1   | udf_reserved_memory     |            0
    (24 rows)
    • 处理方法

    出现内存过载的问题时,如果有内存过载的环境可以实时定位,可以使用有现场环境中的定位流程定位内存问题,如果现场环境已经被破坏(集群重启等导致),则按照无现场环境中的流程进行定位。当前GaussDB的内存管理采用内存上下文机制管理,在内存使用的统计上有着精准的统计和可视化的视图方便查询定位,。

    l   有现场环境

    a.   查询内存统计信息

    表1-1 内存统计信息说明

    参数

    说明

    pv_total_memory_detail

    全局内存信息概况。

    pg_shared_memory_detail

    全局内存上下文内存使用详情。

    pv_thread_memory_context

    线程级内存上下文内存使用详情。

    pv_session_memory_context

    session级内存上下文使用详情,仅在线程池模式开启时生效。

    b.   确定内存占用分类

    根据查询内存统计信息中查询出的内存统计视图结果,根据内存统计视图可以分析出如下结果。

    表1-2 内存占用参数说明

    参数

    说明

    process_used_memory

    数据库进程所使用的内存大小。

    dynamic_used_memory

    已使用的动态内存。

    dynamic_used_shrctx

    全局内存上下文已使用的动态内存。

    shared_used_memory

    已使用的共享内存。

    other_used_memory

    其他已使用的内存大小,一般进程释放后被缓存起来的内存会统计在内。

    如果dynamic_used_memory较大,dynamic_used_shrctx较小,则可以确认是线程和session上内存占用较多,则直接查询线程和session上的内存上下文的内存占用即可确认内存堆积的具体内存上下文。

    如果dynamic_used_memory较大,dynamic_used_shrctxdynamic_used_memory相差不大,则可以确认是全局内存上下文使用的动态内存较大,直接查询全局的内存上下文的内存占用即可确认内存堆积的具体内存上下文。

    如果只有shared_used_memory占用较大,则可以确认是共享内存占用较多,忽略即可。

    如果是other_used_memory较大,一般情况下应该是出现了频繁的内存申请和释放的业务导致内存碎片缓存过多,此时需要联系华为工程师定位解决。

    c.  确定内存堆积原因

    根据确定内存占用分类中查询出来的内存统计信息数据就可以确认数据库进程内存使用过高的原因,一般内存占用较高都是由如下2类原因导致。

    表1-3 内存较高原因

    原因

    说明

    内存堆积

    业务执行时临时内存没有及时释放,导致动态内存持续上涨。

    执行作业时,数据库内部频繁申请和释放内存如创建大量的cache plan的情况下会造成大量的内存碎片,由于底层内存机制的缘故,这些内存碎片不会被操作系统立即回收,而是缓存起来,导致数据库占用的内存持续居高不下。

    常见已知的会导致内存缓存堆积的场景有如下几种:

    l  作业并发过大,且每个并发执行时都会申请和释放大量的内存。

    l  开启GPC,且当pbe多次batch绑定,每bind一次,就会生成一次计划,硬解析一次,导致GPC产生内存碎片。

    l  短时间内并发执行存储过程。

    l  短时间内大量的申请和释放内存的业务场景。

    实际需要

    内存使用内存处于较高状态时,如果从内存统计视图数据上为发现有临时内存未释放的情况,此时就需要关注业务的并发量,执行的语句的复杂度等,业务并发过多也会导致内存占用过高,单条复杂的SQL也会造成使用大量内存的场景出现。

    l   无现场环境

    某些内存过载出现时会导致集群环境重启等,这种情况下没有实时的环境能够定位内存过载的原因是什么,就需要使用如下流程来定位已发生过的内存过载导致的原因。

    a. 查看历史内存占用曲线

    在环境上查看数据库进程使用内存大小的监控数据。根据数据库在一段时间内的内存变化曲线,可以确认内存过载的时间节点,如下图所示。

    411.png

    从历史内存曲线中,可以看出在822日内存出现瞬间上涨的情况。

    b. 分析历史内存统计信息

    根据查看历史内存占用曲线中确认的时间节点信息,使用视图gs_get_history_memory_detail可获得该时间节点的内存快照日志,快照日志以时间戳作为文件名,能够迅速查找,根据快照日志能够快速的找到内存居高的内存上下文信息,然后联系华为工程师协助即可解决。

    如果没有在历史内存统计信息中找到对应时间节点的内存快照信息,此时需要关注该时间节点业务的并发量,执行语句的复杂度等,根据实际业务情况进一步分析内存上涨的原因,需联系华为工程师协助解决。

    遇到内存占用过高或者内存不足报错的场景时可以通过如下流程来分析定位原因并解决。

    1. 获取内存统计信息

    必须要获取出现内存故障的数据库进程上的内存统计信息。

        实时内存统计信息

    查询GaussDB进程总的内存统计信息。

    gaussdb=# select * from pv_total_memory_detail;
     nodename       |       memorytype        | memorymbytes
    ----------------+-------------------------+--------------
     coordinator1   | max_process_memory      |        81920
     coordinator1   | process_used_memory     |        14567
     coordinator1   | max_dynamic_memory      |        34012
     coordinator1   | dynamic_used_memory     |         1851
     coordinator1   | dynamic_peak_memory     |         3639
     coordinator1   | dynamic_used_shrctx     |          394
     coordinator1   | dynamic_peak_shrctx     |          399
     coordinator1   | max_backend_memory      |          648
     coordinator1   | backend_used_memory     |            1
     coordinator1   | max_shared_memory       |        46747
     coordinator1   | shared_used_memory      |        11618
     coordinator1   | max_cstore_memory       |          512
     coordinator1   | cstore_used_memory      |            0
     coordinator1   | max_sctpcomm_memory     |            0
     coordinator1   | sctpcomm_used_memory    |            0
     coordinator1   | sctpcomm_peak_memory    |            0
     coordinator1   | other_used_memory       |         1013
     coordinator1   | gpu_max_dynamic_memory  |            0
     coordinator1   | gpu_dynamic_used_memory |            0
     coordinator1   | gpu_dynamic_peak_memory |            0
     coordinator1   | pooler_conn_memory      |            0
     coordinator1   | pooler_freeconn_memory  |            0
     coordinator1   | storage_compress_memory |            0 
     coordinator1   | udf_reserved_memory     |            0
    (24 rows)

    查看数据库进程全局的内存上下文占用大小,按照内存上下文分类从大到小排序,取top10即可。

    gaussdb=# select contextname, sum(totalsize)/1024/1024 sum, sum(freesize)/1024/1024, count(*) count from pg_shared_memory_detail group by contextname order by sum desc limit 10;
                contextname            |         sum          |       ?column?        | count
    -----------------------------------+----------------------+-----------------------+-------
     IncreCheckPointContext            | 250.8796234130859375 | .00273132324218750000 |     1
     AshContext                        |  64.0950317382812500 | .00772094726562500000 |     1
     DefaultTopMemoryContext           |  60.5699005126953125 |    1.0594177246093750 |     1
     StorageTopMemoryContext           |  16.7601776123046875 | .05357360839843750000 |     1
     GlobalAuditMemory                 |  16.0081176757812500 | .00769042968750000000 |     1
     CBBTopMemoryContext               |  14.9503479003906250 | .04009246826171875000 |     1
     Undo                              |   8.6680450439453125 | .21752929687500000000 |     1
     DoubleWriteContext                |   6.5549163818359375 | .02331542968750000000 |     1
     ThreadPoolContext                 |   5.4042663574218750 | .00525665283203125000 |     1
     GlobalSysDBCacheEntryMemCxt_16384 |   4.2232666015625000 | .89799499511718750000 |    16
    (10 rows)

    查看数据库进程所有线程的内存上下文占用大小,按照内存上下文分类从大到小排序,取top10即可。

    gaussdb=# select contextname, sum(totalsize)/1024/1024 sum, sum(freesize)/1024/1024, count(*) count from pv_thread_memory_context group by contextname order by sum desc limit 10;
               contextname           |         sum          |       ?column?        | count
    ---------------------------------+----------------------+-----------------------+-------
     LocalSysCacheShareMemoryContext | 612.5096435546875000 |   57.4630737304687500 |   543
     StorageTopMemoryContext         | 311.8157348632812500 |    3.2519149780273438 |   543
     DefaultTopMemoryContext         | 168.5756530761718750 |   10.7153015136718750 |   543
     LocalSysCacheMyDBMemoryContext  | 167.4375000000000000 |   65.7499847412109375 |   543
     ThreadTopMemoryContext          | 161.4440002441406250 |    4.0309295654296875 |   543
     CBBTopMemoryContext             | 109.1161880493164063 |    6.7845993041992188 |   543
     LocalSysCacheTopMemoryContext   |  93.4109802246093750 |   13.2236938476562500 |   543
     Timezones                       |  43.2421417236328125 |    1.4333953857421875 |   543
     gs_signal                       |  32.2394561767578125 |    4.9155120849609375 |     1
     Type information cache          |  22.9119262695312500 | .86848449707031250000 |   329
    (10 rows)

    查看数据库进程所有session的内存上下文占用大小,按照内存上下文分类从大到小排序,取top10即可。

    gaussdb=# select contextname, sum(totalsize)/1024/1024 sum, sum(freesize)/1024/1024, count(*) count from pv_session_memory_context group by contextname order by sum desc limit 10;
           contextname       |         sum          |       ?column?        | count
    -------------------------+----------------------+-----------------------+-------
     CachedPlan              | 223.4433593750000000 |   64.6083068847656250 | 12394
     CachedPlanQuery         | 134.7382812500000000 |   42.3366699218750000 | 12596
     SessionTopMemoryContext | 132.3496398925781250 |   25.9272155761718750 |   302
     CachedPlanSource        |  98.6943359375000000 |   28.3841018676757813 | 12897
     CBBTopMemoryContext     |  60.6870880126953125 |    3.0470962524414063 |   302
     GenericRoot             |  35.1962890625000000 |   14.1624069213867188 |   471
     Timezones               |  24.0499572753906250 | .79721069335937500000 |   302
     SPI Plan                |  21.0664062500000000 |    6.8149719238281250 |  2396
     AdaptiveCachedPlan      |  17.5449218750000000 |    4.7733078002929688 |   546
     Prepared Queries        |  16.4062500000000000 |    7.5508117675781250 |   300
    (10 rows)

        历史内存统计信息

    历史内存信息只有在没有实时现场环境的时候才会被用到,通过历史内存统计信息来定位过去发生过的内存过载的原因。

    查询视图gs_get_history_memory_detail可以获得数据库在过去所有时间段的内存使用超过90%时的内存使用详情,如下所示。

    gaussdb=# select * from gs_get_history_memory_detail(NULL) order by memory_info desc limit 10;
              memory_info
    -------------------------------
     mem_log-2023-03-10_205125.log
     mem_log-2023-03-10_205115.log
     mem_log-2023-03-10_205104.log
     mem_log-2023-03-10_205054.log
     mem_log-2023-03-10_205043.log
     mem_log-2023-03-10_205032.log
     mem_log-2023-03-10_205022.log
     mem_log-2023-03-10_205012.log
     mem_log-2023-03-10_205002.log
     mem_log-2023-03-10_204951.log
    (10 rows)

    选取其中一个log文件,执行如下查询语句即可阅览log内容,记载了全局的内存概况与全局级内存上下文,线程级内存上下文,session级内存上下的top20内存上下文占用详情,如下所示。

    gaussdb=# select * from gs_get_history_memory_detail('mem_log-2023-03-10_205125.log');
                                         memory_info
    --------------------------------------------------------------------------------------
     {
             "Global Memory Statistics":     {
                     "Max_dynamic_memory":   34012,
                     "Dynamic_used_memory":  3645,
                     "Dynamic_peak_memory":  3664,
                     "Dynamic_used_shrctx":  401,
                     "Dynamic_peak_shrctx":  401,
                     "Max_backend_memory":   648,
                     "Backend_used_memory":  1,
                     "other_used_memory":    0
             },
             "Memory Context Info":  {
                     "Memory Context Detail":        {
                             "Context Type": "Shared Memory Context",
                             "Memory Context":       {
                                     "context":      "IncreCheckPointContext",
                                     "freeSize":     2864,
                                     "totalSize":    263066352
                             },
                            ...
                     },
                     "Memory Context Detail":        {
                             "Context Type": "Session Memory Context",
                             "Memory Context":       {
                                     "context":      "CachedPlan",
                                     "freeSize":     68041368,
                                     "totalSize":    235937792
                             },
                             ...
                     },
                     "Memory Context Detail":        {
                             "Context Type": "Thread Memory Context",
                             "Memory Context":       {
                                     "context":      "LocalSysCacheShareMemoryContext",
                                     "freeSize":     60431360,
                                     "totalSize":    644141760
                             },
                             ...
                     }
             }
    (322 rows)

    2.  分析内存占用分类

    根据获取内存统计信息中查询获得的内存占用概况可分析如下:

               如果dynamic_used_memory较大,dynamic_used_shrctx较小,则可以确认是线程和session上内存占用较多。

               如果dynamic_used_memory较大,dynamic_used_shrctxdynamic_used_memory相差不大,则可以确认是全局内存上下文使用的动态内存较大。

               如果只有shared_used_memory占用较大,则可以确认是共享内存占用较多,忽略即可。

               如果是other_used_memory较大,一般情况是由于业务执行时频繁的内存申请和释放导致内存碎片缓存过多。

    针对这几种种情况,分别按照下面的4类定位方法定位即可。

    a.  全局内存上下文占用较高

    有现场环境

    查询如下语句即可确认是哪个内存上下文占用内存较高。

    gaussdb=# select contextname, sum(totalsize)/1024/1024 sum, sum(freesize)/1024/1024, count(*) count from pg_shared_memory_detail group by contextname order by sum desc limit 10;
                contextname            |         sum          |       ?column?        | count
    -----------------------------------+----------------------+-----------------------+-------
     IncreCheckPointContext            | 250.8796234130859375 | .00273132324218750000 |     1
     AshContext                        |  64.0950317382812500 | .00772094726562500000 |     1
     DefaultTopMemoryContext           |  60.5699005126953125 |    1.0594177246093750 |     1
     StorageTopMemoryContext           |  16.7601776123046875 | .04942321777343750000 |     1
     GlobalAuditMemory                 |  16.0081176757812500 | .00769042968750000000 |     1
     CBBTopMemoryContext               |  14.9503479003906250 | .04009246826171875000 |     1
     Undo                              |   8.6680450439453125 | .20516967773437500000 |     1
     DoubleWriteContext                |   6.5549163818359375 | .02331542968750000000 |     1
     ThreadPoolContext                 |   5.3873443603515625 | .00525665283203125000 |     1
     GlobalSysDBCacheEntryMemCxt_16384 |   4.3115692138671875 |    1.0470581054687500 |    16
    (10 rows)

    确定内存上下文之后,以IncreCheckPointContext为例,查询视图gs_get_shared_memctx_detail,确定内存堆积的代码位置。

    gaussdb=# select * from gs_get_shared_memctx_detail('IncreCheckPointContext');
              file           | line |   size
    -------------------------+------+-----------
     ipci.cpp                |  476 |        64
     pagewriter.cpp          |  298 |      1024
     ipci.cpp                |  498 |      4096
     pagewriter.cpp          |  322 |  19632000
     pagewriter.cpp          |  317 |  33669120
     storage_buffer_init.cpp |   90 | 209756160
    (6 rows)

    从上述查询结果可以看出,在代码storage_buffer_init.cpp90行申请了大量的内存,可能存在内存堆积不释放的问题。

    无现场环境

    若存在内存过载时间节点的内存快照信息,则在内存统计信息中找到Shared Memory Context类型的top20的内存上下文申请详情,即可确认内存堆积的原因。

    若不存在内存过载时间节点的内存快照信息,请联系华为工程师定位解决。

    总结:使用上面2种方法找到内存占用过多的内存上下文后,可进行初步判断,在数据库内核执行业务时,一般占用较多的全局内存上下文有“IncreCheckPointContext”,“DefaultTopMemoryContext”,如果是这两个context占用较多,则需要减小业务的并发来降低内存占用;如果是其他内存上下文,可能是业务执行过程出现内存堆积,请联系华为工程师解决。

    b. 线程级内存上下文占用内存较高

    有现场环境

    查询如下语句即可确认是哪个内存上下文占用内存较高。

    gaussdb=# select contextname, sum(totalsize)/1024/1024 sum, sum(freesize)/1024/1024, count(*) count from pv_thread_memory_context group by contextname order by sum desc limit 10;
               contextname           |         sum          |       ?column?        | count
    ---------------------------------+----------------------+-----------------------+-------
     LocalSysCacheShareMemoryContext | 641.0926513671875000 |   60.0820159912109375 |   543
     StorageTopMemoryContext         | 311.8157348632812500 |    3.1896591186523438 |   543
     LocalSysCacheMyDBMemoryContext  | 175.0625000000000000 |   65.0446166992187500 |   543
     DefaultTopMemoryContext         | 168.5756530761718750 |   10.7153015136718750 |   543
     ThreadTopMemoryContext          | 161.9752502441406250 |    4.1196441650390625 |   543
     CBBTopMemoryContext             | 109.1161880493164063 |    6.7845993041992188 |   543
     LocalSysCacheTopMemoryContext   |  93.4109802246093750 |   13.2236938476562500 |   543
     Timezones                       |  43.2421417236328125 |    1.4333953857421875 |   543
     gs_signal                       |  32.2394561767578125 |    4.9155120849609375 |     1
     Type information cache          |  23.8869018554687500 | .90544128417968750000 |   343
    (10 rows)

    确定内存上下文之后,以StorageTopMemoryContext为例,查询视图gs_get_thread_memctx_detail(第一个入参为线程ID,可以通过查询视图gs_thread_memory_context获得 ),确定内存堆积的代码位置。

    gaussdb=# select * from gs_get_thread_memctx_detail(140639273547520,'StorageTopMemoryContext');
         file     | line |  size
    --------------+------+--------
     syncrep.cpp  | 1608 |     32
     elog.cpp     | 2008 |     16
     fd.cpp       | 2734 |    128
     syncrep.cpp  | 1568 |     32
     deadlock.cpp |  175 |    512
     deadlock.cpp |  169 | 342656
     deadlock.cpp |  157 |  85664
     deadlock.cpp |  146 |  21416
     deadlock.cpp |  144 |  32112
     deadlock.cpp |  136 |  10712
     deadlock.cpp |  135 |  10712
     deadlock.cpp |  128 |  85664
     deadlock.cpp |  126 |  21416
    (13 rows)

    从上述查询结果可以看出,在代码deadlock.cpp169行申请了大量的内存,可能存在内存堆积不释放的问题。

    无现场环境

    若存在内存过载时间节点的内存快照信息,则在内存统计信息中找到Thread Memory Context类型的top20的内存上下文申请详情,即可确认内存堆积的原因。

    若不存在内存过载时间节点的内存快照信息,请联系华为工程师定位解决。

    总结:使用上面2种方法找到内存占用过多的内存上下文后,可进行初步判断,在数据库内核执行业务时,一般占用较多的全局内存上下文有“LocalSysCacheShareMemoryContext”,“StorageTopMemoryContext”,如果是这两个context占用较多,则需要减小业务的并发来降低内存占用;如果是其他内存上下文,可能是业务执行过程出现内存堆积,请联系华为工程师解决。

    c. session级内存上下文占用内存较高

    有现场环境

    查询如下语句即可确认是哪个内存上下文占用内存较高。

    gaussdb=# select contextname, sum(totalsize)/1024/1024 sum, sum(freesize)/1024/1024, count(*) count from pv_session_memory_context group by contextname order by sum desc limit 10;
            contextname         |         sum          |       ?column?        | count
    ----------------------------+----------------------+-----------------------+-------
     CachedPlan                 | 226.1093750000000000 |   67.1747817993164063 | 12450
     CachedPlanQuery            | 134.8027343750000000 |   41.8541030883789063 | 12612
     SessionTopMemoryContext    | 132.1605682373046875 |   26.1002349853515625 |   301
     CachedPlanSource           |  98.7617187500000000 |   28.4135513305664063 | 12912
     CBBTopMemoryContext        |  60.4861373901367188 |    3.0370101928710938 |   301
     Timezones                  |  23.9703216552734375 | .79457092285156250000 |   301
     SPI Plan                   |  21.1307907104492188 |    6.8435440063476563 |  2412
     GenericRoot                |  19.9628906250000000 |    7.7032165527343750 |   374
     Prepared Queries           |  16.4062500000000000 |    7.5508117675781250 |   300
     unnamed prepared statement |  14.3437500000000000 |    6.6462554931640625 |   300
    (10 rows)

    确定内存上下文之后,以CachedPlan为例,查询视图gs_get_session_memctx_detail,确定内存堆积的代码位置。

    gaussdb=# select * from gs_get_session_memctx_detail('CachedPlanQuery');
         file      | line |  size
    ---------------+------+---------
     copyfuncs.cpp | 2607 | 5031680
     copyfuncs.cpp | 7013 | 4176736
     copyfuncs.cpp | 7016 | 2088368
     copyfuncs.cpp | 5062 | 6918144
     copyfuncs.cpp | 3461 |  403552
     copyfuncs.cpp | 3397 | 2727104
     copyfuncs.cpp | 3401 |  487368
     datum.cpp     |  150 |    2048
     copyfuncs.cpp | 2572 | 1113728
     copyfuncs.cpp | 6204 |      32
     copyfuncs.cpp | 6206 |      32
     copyfuncs.cpp | 7021 | 4267200
     copyfuncs.cpp | 7037 | 2832000
     copyfuncs.cpp | 7048 | 2066400
     bitmapset.cpp |   94 |  134400
     copyfuncs.cpp | 3430 |   96000
     copyfuncs.cpp | 2847 | 2150400
     copyfuncs.cpp | 2551 | 5126400
     copyfuncs.cpp | 3984 |  105600
     list.cpp      |  105 |  254400
     list.cpp      |  108 |  796800
     copyfuncs.cpp | 3835 | 7065600
     copyfuncs.cpp | 2451 | 1056000
     copyfuncs.cpp | 2453 |  244800
     copyfuncs.cpp | 3840 |  230400
     copyfuncs.cpp | 2895 | 1113600
     copyfuncs.cpp | 3442 |   38400
     copyfuncs.cpp | 2645 |  115200
     list.cpp      |  166 |   19200
     namespace.cpp | 3853 |  144000
     list.cpp      | 1460 |  288000
     copyfuncs.cpp | 2910 |   38400
     copyfuncs.cpp | 2762 | 1075200
     copyfuncs.cpp | 3953 |   67200
     copyfuncs.cpp | 3000 |   96000
     copyfuncs.cpp | 5876 |   28800
     copyfuncs.cpp | 2619 |    2400
    (37 rows)

    从上述查询结果可以看出,在代码copyfuncs.cpp3835行申请了大量的内存,可能存在内存堆积不释放的问题。

    无现场环境

    若存在内存过载时间节点的内存快照信息,则在内存统计信息中找到Session Memory Context类型的top20的内存上下文申请详情,即可确认内存堆积的原因。

    若不存在内存过载时间节点的内存快照信息,请联系华为工程师定位解决。

    总结:使用上面2种方法找到内存占用过多的内存上下文后,可进行初步判断,在数据库内核执行业务时,一般占用较多的全局内存上下文有“CachedPlan”,“CachedPlanQuery”,“CachedPlanSource”,“SessionTopMemoryContext”,如果是这几个context占用较多,则需要减小业务的并发来降低内存占用;如果是其他内存上下文,可能是业务执行过程出现内存堆积,请联系华为工程师解决。

    d. 其他内存占用较高

    内存碎片过多导致内存缓存过多

    数据库执行作业时,数据库内部频繁申请和释放内存如创建大量的cache plan的情况下会造成大量的内存碎片,由于底层内存机制的缘故,这些内存碎片不会被操作系统立即回收,而是缓存起来,数据库在统计的时候会将其计算在other_used_memory里面,如下所示。

    gaussdb=# select * from pv_total_memory_detail;
     nodename       |       memorytype        | memorymbytes
    ----------------+-------------------------+--------------
     coordinator1   | max_process_memory      |        81920
     coordinator1   | process_used_memory     |        24567
     coordinator1   | max_dynamic_memory      |        34012
     coordinator1   | dynamic_used_memory     |         1851
     coordinator1   | dynamic_peak_memory     |         3639
     coordinator1   | dynamic_used_shrctx     |          394
     coordinator1   | dynamic_peak_shrctx     |          399
     coordinator1   | max_backend_memory      |          648
     coordinator1   | backend_used_memory     |            1
     coordinator1   | max_shared_memory       |        46747
     coordinator1   | shared_used_memory      |        11618
     coordinator1   | max_cstore_memory       |          512
     coordinator1   | cstore_used_memory      |            0
     coordinator1   | max_sctpcomm_memory     |            0
     coordinator1   | sctpcomm_used_memory    |            0
     coordinator1   | sctpcomm_peak_memory    |            0
     coordinator1   | other_used_memory       |         11013
     coordinator1   | gpu_max_dynamic_memory  |            0
     coordinator1   | gpu_dynamic_used_memory |            0
     coordinator1   | gpu_dynamic_peak_memory |            0
     coordinator1   | pooler_conn_memory      |            0
     coordinator1   | pooler_freeconn_memory  |            0
     coordinator1   | storage_compress_memory |            0
     coordinator1   | udf_reserved_memory     |            0
    (24 rows)

    其他原因导致内存未及时释放

    此处需要注意:other_used_memory过大不全部都是因为内存碎片导致的,也可能是如下原因:

    1)   业务代码中存在没有在内存上下文上申请内存直接使用了malloc接口申请内存的地方,且出现了内存堆积。

    2)   第三方开源软件存在内存未及时释放的场景。

    出现这两种情况时,需要联系华为工程师协助解决。

    3.  解决方案

      内存堆积导致内存满

    方案:出现内存堆积长时间不释放时,需要通过做主备切换来降低内存的使用。

      业务原因导致内存满

    方案:修改客户端作业,降低并发数或者修改SQL语句,使其在执行时不占用大量内存,请联系华为工程师协助给出详细的解决方案。

      other内存缓存过多导致内存满

    方案一:如果是由于业务场景导致的other内存缓存过高,则可以通过调整执行计划相关的参数或者从客户端侧调整业务来解决内存过高的问题,需要根据具体业务场景确定修改方案,请联系华为工程师协助给出详细的解决方案。

    方案二:出现内存堆积长时间不释放时,且无法通过调整业务来降低内存时则需要通过做主备切换来降低内存的使用。

    5.逻辑解码常见问题

    5.1 逻辑解码连接失败

    • 故障现象

    连接数据库解码失败,报拒绝连接错误。

    • 故障原因

    可能原因有以下:

    l   用户没有逻辑解码权限。

    报错日志如下:

    FATAL:  must be system admin or replication role or a member of the gs_role_replication role to use replication slots

    l   用户名和用户所在机器的IP未在数据库解码CNDN节点上配置白名单。

    报错日志如下(以用户Ruby为例,其中的X.X.X.X代指用户所在机器的IP):

    FATAL:  no pg_hba.conf entry for replication connection from host "X.X.X.X", user "Ruby", SSL off

    l   连接CN解码时,指定用户未在主DN上配置来自CN连接的白名单。

    报错日志如下:

    FATAL:  [FATAL] Connection from CN must use trust or gss auth method.
    • 处理方法

    l   针对用户没有解码权限场景:

    为解码用户(如下以Ruby为例)赋予解码角色权限,执行以下SQL语句:

    GRANT gs_role_replication TO Ruby;

    l   针对连接CN解码白名单未配置场景:

    使用gs_guc工具,为用户名(以Ruby为例)和用户所在机器IP(如下用X.X.X.X指代,实际使用时需替换为正确IP)在数据库解码节点上配置白名单:

    gs_guc reload -Z coordinator -N all -I all -h 'host replication Ruby  X.X.X.X/32 sha256'

    l   针对直连DN解码白名单未配置场景:

    使用gs_guc工具,为用户名(以Ruby为例)和用户所在机器IP(如下用X.X.X.X指代,实际使用时需替换为正确IP)在数据库解码节点上配置白名单:

    gs_guc reload -Z datanode -N all -I all -h 'host replication Ruby  X.X.X.X/32 sha256'

    l   针对连接CN解码时,指定用户未在主DN上配置来自CN连接的白名单:

    使用gs_guc工具,为用户名(以Ruby为例)和解码连接CN所在机器IP(如下用X.X.X.X指代,实际使用时需替换为正确IP)在主DN节点上配置白名单:

    gs_guc reload -Z datanode -N all -I all -h 'host replication Ruby  X.X.X.X/32 trust'

    注意:如果需要为同一用户同时配置直连DN解码以及连接CN解码白名单,在相关DN上则不能使用gs_guc工具,而需要手动将单引号中的内容(如:host replication Ruby  X.X.X.X/32 trust)追加到对应DN节点的pg_hba.conf文件中。该文件存在于指定DN的实例路径下。

    5.2 逻辑解码内存故障

    • 故障现象

    并行解码场景读取日志线程或解码线程占用内存过多,导致产生内存不足报错。连接解码DN(如果为连接CN解码则需连接某个主DN)使用如下SQL函数查询,发现contextnameParallelDecodeDispatcherParallelDecodeLog的内存上下文占用内存比较多:

    select contextname, sum(totalsize)/1024/1024 sum, sum(freesize)/1024/1024, count(*) count from pg_shared_memory_detail group by contextname order by sum desc limit 10;
    • 故障原因

    可能原因有以下:

    l   ParallelDecodeDispatcher占用内存过多。此种场景为读取日志线程占用内存过多,需等待解码线程消费日志以降低内存占用。

    l   ParallelDecodeLog占用内存过多。此种场景为解码线程占用内存过多,需等待发送线程发送日志以降低内存占用。

    • 处理方法

    l   针对读取日志线程占用内存过多场景,建议增加解码线程数量。

    l   针对解码线程占用内存过多场景,设置max-txn-in-memorymax-reorderbuffer-in-memory配置选项,详细内容参考《特性指南》中“逻辑复制 > 逻辑解码 > 逻辑解码选项”章节。

    5.3 逻辑解码复制槽异常

    • 故障现象

    逻辑复制槽不推进或推进缓慢。

    • 故障原因

    可能原因有以下:

    l   未使用并行解码。

    l   已使用并行解码场景:

      无法正确读取日志。

       存在长事务。

     用户未及时反馈接收到的日志位置。

    • 处理方法

                                    步骤 1     判断当前逻辑复制槽是否使用并行解码。

    在解码节点上调用SQL函数:

    select * from gs_get_parallel_decode_status();

    返回列中parallel_decode_num表示解码并行度。如果没有相应复制槽的信息,说明并未使用并行解码,可以考虑使用并行解码功能。

                                    步骤 2     观察读取日志线程是否正常读取日志。

    多次调用SQL函数:

    select * from gs_get_parallel_decode_status();

    观察相应复制槽的reader_lsn列是否稳定增长,如稳定增长说明能正常读取日志,需联系运维人员检查日志是否出现损坏。

                                    步骤 3     观察是否有运行时间长的事务。

    查看系统视图pg_stat_activity,观测是否有运行时间长的写事务未结束,发现用长事务时,可以联系业务人员或运维人员尽快结束相关事务。

                                    步骤 4     观察等待解码日志队列和等待拼接日志队列是否已满。

    观察步骤2中相应复制槽的返回列read_change_queue_length(对应等待解码日志队列长度)和decode_change_queue_length(对应等待拼接日志队列长度)中各解码线程对应的队列长度是否已满(队列长度等于parallel-queue-size - 1,默认参数下相应值为127,表示队列已满),如已满表示解码结果发送遭到阻塞,应及时取用解码结果或反馈接收到的日志位置。

                                    步骤 5     联系华为工程师确认客户端是否接受到逻辑解码结果并反馈接受日志。

    ----结束

    6.分区表常见问题

    6.1 分区表查询性能异常

    • 故障现象

    使用分区表进行相关查询业务,SQL性能慢。

    • 故障原因

    可能原因有以下:

    l   分区索引失效,顺序扫描导致的SQL性能慢

    l   分区表无法进行分区剪枝导致的SQL性能慢

    l   SQL计划选择非最优导致的SQL性能慢

    • 处理方法

                                    步骤 1     判断是否存在索引异常的行为。

                                    步骤 2     部分分区DDL如果不带UPDATE GLOBAL INDEX子句,会导致分区表Global索引失效。同时用户也可以使用ALTER TABLE或者ALTER INDEX使分区表索引失效。当分区表或者部分分区上索引不可用后,会导致查询SQL无法走索引扫描,从而出现慢SQL场景。

    l   查询索引类型和状态

    分区表的索引有两种,Local索引和Global索引。Local索引会在每个分区创建一个索引,Global索引会在整个分区表上创建一个全局索引。对于Local索引,需要确认整个索引自身状态和目标查询分区的索引状态;对于Global索引,只需要确认整个索引自身状态。

    可以通过系统表pg_index查询分区表的所有索引,并获取索引是否可用的状态,其索引类型在pg_class中给出。下面给出了查询分区表t1的全部索引状态及索引类型的命令参考:

    gaussdb=# select c.relname, i.indisusable, c.relkind from pg_class c join pg_index i on c.oid=i.indexrelid
        join pg_class r on i.indrelid=r.oid where r.relname='t1';
          relname       | indisusable | relkind 
    --------------------+-------------+---------
     t1_c1_idx          | t           | i
     t1_c2_tableoid_idx | f           | I

    其中indisusable字段为't'表示该索引可用,为'f'表示索引已经失效,在查询业务中该索引无法使用;relkind'i'表示该索引为Local索引,为'I'表示该索引为Global索引。

    或者通过元语句\d命令查询分区表的索引信息:

    gaussdb=# \d t1
          Table "public.t1"
     Column |  Type   | Modifiers 
    --------+---------+-----------
     c1     | integer | 
     c2     | integer | 
    Indexes:
        "t1_c1_idx" btree (c1) LOCAL TABLESPACE pg_default
        "t1_c2_tableoid_idx" btree (c2) TABLESPACE pg_default UNUSABLE
    Partition By RANGE(c1)
    Number of partitions: 2 (View pg_partition to check each partition range.)

    可以看到表t1的两条索引t1_c1_idxt1_c2_tableoid_idx被列出。其中t1_c1_idx带有LOCAL关键字,表示该索引为Local索引,t1_c2_tableoid_idx没有LOCAL关键字,表示该索引为Global索引。同时t1_c2_tableoid_idx标记了UNUSABLE,表示该索引不可用。

    l   查询索引分区状态

    对于Local索引,还需要确认目标查询分区的索引状态,这个信息可以从系统表pg_partition中查询。

    gaussdb=# select p.relname,p.indisusable from pg_partition p join pg_class c on p.parentid=c.oid where c.relname='t1_c1_idx';
      relname  | indisusable 
    -----------+-------------
     p1_c1_idx | t
     p2_c1_idx | f
    (2 rows)

    可以看到索引分区p1_c1_idx可用,索引分区p2_c1_idx不可用。在分区p2上的查询无法使用索引t1_c1_idx

    l   重建异常的索引/索引分区

    如果分区表的索引/索引分区状态异常,会导致查询SQL无法走索引扫描,需要重建索引/索引分区。重建索引的命令如下:

    ALTER INDEX t1_c2_tableoid_idx REBUILD;

    重建索引分区的命令如下:

    ALTER INDEX t1_c1_idx REBUILD PARTITION p2_c1_idx;

                                    步骤 3     判断分区表是否存在剪枝异常的场景。

                                    步骤 4     当分区表的分区键所在列存在条件时,可以触发分区剪枝。数据库会在优化器/执行器阶段识别到需要扫描的分区,而不会扫描全部分区。只有分区表的业务是顺序扫描,或者使用Local索引进行扫描时候,才可能触发分区剪枝;Global索引不会触发分区剪枝,这是因为Global索引是在整个分区表上创建的单个索引,不存在剪枝的概念。

                                    步骤 5     剪枝是被动触发的,当查询业务满足分区剪枝条件时,会自动触发分区剪枝。

    l   判断是否触发了分区剪枝

    可以通过查询计划来判断是否触发了分区剪枝。当扫描分区数少于分区总数时,即触发了分区剪枝。分区剪枝分为静态剪枝和动态剪枝,静态剪枝是指在优化器阶段就能识别到裁剪的分区;动态剪枝是指优化器阶段只能确定可以进行剪枝,但不知道具体裁剪到哪个分区,在执行器阶段才会确定裁剪的目标分区。

    下面的业务触发了分区静态剪枝,计划中的'Iterations: 1'表示扫描了1个分区,'Selected Partitions:  1'表示扫描的目标分区下标是1

    这个下标是一个逻辑数组下标,对于范围分区/间隔分区,按照分区定义上界值升序排列;对于列表分区,按照第一个枚举值升序排列;对于哈希分区,按照计算后的哈希值升序排列。可以通过函数pg_get_tabledef获取分区定义,其列出的分区即为该对应下标。

    gaussdb=# explain select * from t1 where c1 < 100;
                                  QUERY PLAN                               
    -----------------------------------------------------------------------
     Partition Iterator  (cost=0.00..27.86 rows=716 width=8)
       Iterations: 1
       ->  Partitioned Seq Scan on t1  (cost=0.00..27.86 rows=716 width=8)
             Filter: (c1 < 100)
             Selected Partitions:  1
    (5 rows)

    下面的业务没有触发分区剪枝,计划中的'Iterations: 2'表示扫描了2个分区,'Selected Partitions:  1..2'表示扫描的目标分区下标是12。可以看到扫描的分区数等于分区表的总分区数,表示未进行任何分区剪枝。

    gaussdb=# explain select * from t1;
                                   QUERY PLAN                               
    ------------------------------------------------------------------------
     Partition Iterator  (cost=0.00..31.49 rows=2149 width=8)
       Iterations: 2
       ->  Partitioned Seq Scan on t1  (cost=0.00..31.49 rows=2149 width=8)
             Selected Partitions:  1..2
    (4 rows)

    下面的业务触发了分区动态剪枝,计划中的'Iterations: PART''Selected Partitions:  PART'表示优化器识别到分区表可以进行分区剪枝,但具体

    gaussdb=# prepare p1 as select * from t1 where c1 < $1;
    gaussdb=# explain execute p1(100);
                                             QUERY PLAN                                         
    --------------------------------------------------------------------------------------------
     Partition Iterator  (cost=9.80..28.75 rows=716 width=8)
       Iterations: PART
       ->  Partitioned Bitmap Heap Scan on t1  (cost=9.80..28.75 rows=716 width=8)
             Recheck Cond: (c1 < $1)
             Selected Partitions:  PART
             ->  Partitioned Bitmap Index Scan on t1_c1_idx  (cost=0.00..9.62 rows=716 width=0)
                   Index Cond: (c1 < $1)
                   Selected Partitions:  PART
    (8 rows)

    l   支持分区剪枝的场景

    当分区键所在列存在条件时,可以进行分区剪枝。条件剪枝支持场景为:比较表达式(<<==>=>)、逻辑表达式(ANDOR)、数组表达式。需要注意的是,列表和哈希分区不支持除等号外的其他比较表达式的剪枝。如果条件全为常量,则进行静态剪枝;如果条件带有参数、部分隐式转换、immutable函数,则进行动态剪枝。

    当分区表作为参数化路径计划的内表且分区键所在列为索引检索条件时,支持动态剪枝。

    l   不支持分区剪枝的场景

    分区键所在列的条件为子查询表达式、无法直接强转的类型转换、stable/volatile函数时,不支持分区剪枝。

    分区表在生成除参数化路径外的其他JOIN计划时,不支持动态剪枝。

    l   业务改写适配分区剪枝

    当业务设计不合理,导致原本逻辑上可以走分区剪枝的计划,最终未走分区剪枝时,可以改写业务以适配分区剪枝,从而提升分区表查询业务性能。比如使用HINT固定计划、在客户端修改绑参类型等方法。

                                    步骤 6     判断分区表是否是因为SQL计划选择非最优。

                                    步骤 7     参考执行计划常见问题。

    ----结束

    6.2 分区表DQL/DML业务报错

    • 故障现象

    使用分区表进行DQL/DML业务,出现业务报错。

    • 故障原因

    导致分区表DQL/DML业务报错的原因有很多,需要根据具体的报错内容和报错的业务模型,来判断属于哪些场景。

    常见的可能原因有以下:

    l   开启行迁移开关后,并发UPDATE/DELETE报错。

    l   关闭行迁移开关后,跨分区更新导致的报错。

    l   插入数据无法路由到已有分区导致的报错。

    • 处理方法

                                    步骤 1     判断是否为开启行迁移开关导致的并发UPDATE/DELETE报错

    l   报错场景

    如果业务报错为以下两种场景之一,则说明为开启行迁移开关导致的并发UPDATE/DELETE报错。

    --UPDATE业务报错
    ERROR:  partition table update conflict
    DETAIL:  disable row movement of table can avoid this conflict
    --DELETE业务报错
    ERROR:  partition table delete conflict
    DETAIL:  disable row movement of table can avoid this conflict

    l   原因分析

    开启行迁移开关后,允许通过更新分区键的方式,将数据从一个分区更新到另一个分区;关闭行迁移开关后,如果业务试图将数据从一个分区更新到另一个分区,则业务会抛出报错。

    开启/关闭行迁移开关的方法是创建分区表时申明ENABLE/DISABLE ROW MOVEMENT子句。也可以通过ALTER TABLE命令来修改:

    --开启分区表t1的行迁移开关
    ALTER TABLE t1 ENABLE ROW MOVEMENT;
     
    --
    关闭分区表t1的行迁移开关
    ALTER TABLE t1 DISABLE ROW MOVEMENT;

    开启行迁移开关后,并发UPDATE/DELETE业务可能会报错。原因如下:

    UPDATEDELETE操作对于旧数据都是标记为已删除。在打开行迁移开关情况下,如果更新分区键时,导致了跨分区更新,内核会把旧分区中旧数据标记为已删除,在新分区中新增加一条数据,无法通过旧数据找到新数据。

    UPDATEUPDATE并发、DELETEDELETE并发、UPDATEDELETE并发三个并发场景下,如果并发操作同一行数据时,数据跨分区和非跨分区结果有不同的行为。

    a.  对于数据非跨分区结果,第一个操作执行完后,第二个操作不会报错。

    n   如果第一个操作是UPDATE,第二个操作能成功找到最新的数据,之后对新数据操作。

    n   如果第一个操作是DELETE,第二个操作看到当前数据已经被删除而且找不到最新数据,就终止操作。

    b.  对于数据跨分区结果,第一个操作执行完后,第二个操作会报错。

    n   如果第一个操作是UPDATE,由于新数据在新分区中,第二个操作不能成功找到最新的数据,就无法操作,之后会报错。

    n   如果第一个操作是DELETE,第二个操作看到当前数据已经被删除而且找不到最新数据,但无法判断删除旧数据的操作源于UPDATE还是DELETE。如果是UPDATE,报错处理。如果是DELETE,终止操作,为了保持数据的正确性,同样报错处理。

    l   处理方法

    如果业务明确不存在跨分区更新分区键的业务,可以关闭关闭行迁移开关。

    如果业务确实存在跨分区更新分区键的业务,需要串行执行业务才能解决问题;或者考虑更换分区键。

                                    步骤 2     判断是否为关闭行迁移开关导致的跨分区更新报错

    l   报错场景

    如果业务报错为以下场景,则说明为关闭行迁移开关导致的跨分区更新报错。

    ERROR:  fail to update partitioned table "t1"
    DETAIL:  disable row movement

    l   原因分析

    关闭行迁移开关后,如果业务试图将数据从一个分区更新到另一个分区,则业务会抛出报错。

    l   处理方法

    如果业务确实存在跨分区更新分区键的业务,可以考虑打开行迁移开关,但需考虑是否可能会存在并发报错场景;或者考虑更换分区键。

                                    步骤 3     判断是否为插入数据无法路由到已有分区导致的报错

    l   报错场景

    如果业务报错为以下场景,则说明为插入数据无法路由到已有分区导致的报错。

    ERROR:  inserted partition key does not map to any table partitiont

    l   原因分析

    导致这种场景出现的原因一般有两种,一种是因为分区定义不合理导致的业务异常,另一种是分区键类型设计不合理导致的比较异常。

    分区定义不合理是指业务定义的分区结构无法满足全量表数据的匹配,有部分数据不属于任一已有分区。比如下面的业务就会抛出这种报错:

    CREATE TABLE t1 (
     c1 integer,
     c2 integer
    )
    PARTITION BY RANGE (c1)
    (
     PARTITION p1 VALUES LESS THAN (100),
     PARTITION p2 VALUES LESS THAN (200)
    )
    DISABLE ROW MOVEMENT;
    INSERT INTO t1 VALUES(1,1),(201,1);

    分区键类型设计不合理是指使用了不合理的分区键类型,导致数据的比较规则并不符合用户预期场景。比如按照字典序规则,字符串'9''100'大。下面的业务就会抛出这种报错:

    CREATE TABLE t1 (
     c1 varchar,
     c2 integer
    )
    PARTITION BY RANGE (c1)
    (
     PARTITION p1 VALUES LESS THAN ('100'),
     PARTITION p2 VALUES LESS THAN ('200')
    )
    DISABLE ROW MOVEMENT;
    INSERT INTO t1 VALUES('9',1);

                                    步骤 4     如果是分区定义不合理导致的业务异常,需要重新设计分区结构。可以使用分区DDL命令,比如ADD分区、SPLIT分区、MERGE分区等来调整分区结构。

                                    步骤 5     如果是分区键类型设计不合理导致的比较异常,目前不支持修改分区键所在列的类型,需要重新创建分区表。

    ----结束

    6.3 分区表DDL业务报错

    • 故障现象

    使用分区表进行分区DDL业务,出现业务报错。

    • 故障原因

    分区DDL操作有新增(Add)、删除(Drop)、交换(Exchange)、清空(Truncate)、分割(Split)、合并(Merge)、移动(Move)、重命名(Rename)共8种,不同的分区DDL有对应的规格限制,用户如果试图越过规格限制进行该类操作,会导致业务报错。

    • 处理方法

    在《特性指南》中“分区表 > 分区表运维管理”章节列出了分区DDL操作的使用方法和注意事项,这里给出具体的规格限制,按照不同的规格限制进行排查处理。

    l   新增分区

       新增分区的名称不能与该分区表已有分区的名称相同。

        若新增RANGE分区,新增分区的分区键值要大于分区表中最后一个范围分区的上边界。

      若新增LIST分区,新增分区的分区键值不能与现有分区键值重复。

       RANGE分区表定义有MAXVALUE,或LIST分区定义有DEFAULT,无法新增分区。

      不支持新增HASH分区。

    l   删除分区

      当分区表只有一个分区时,不能删除该分区。

     不支持删除HASH分区。

      删除分区会使得Global索引失效,可以通过UPDATE GLOBAL INDEX子句来同步更新Global索引,或者用户自行重建Global索引。

    l   交换分区

      执行交换分区时,可以申明WITH/WITHOUT VALIDATION,表明是否校验普通表数据满足目标分区的分区键约束规则(默认开启校验)。

      如果申明WITHOUT VALIDATION,且交换的数据不完全属于目标分区,会导致分区表后续业务出现不可预知的后果。

        可以申明WITH VALIDATION VERBOSE,数据库会将不满足目标分区的分区键约束规则的数据,插入到分区表的其他分区中,最后再进行普通表与目标分区的交换。

     进行交换的普通表和分区的列数目、列结构、列信息需要完全一致,包括已被删除的列也需严格一致。

       进行交换的普通表索引和分区表Local索引个数相同,且对应索引的信息严格一致。

       进行交换的普通表索引和分区表压缩信息、表约束严格一致。

      进行交换的普通表索引和分区表不可以有动态数据脱敏,行访问控制约束。

       交换分区会使得Global索引失效,可以通过UPDATE GLOBAL INDEX子句来同步更新Global索引,或者用户自行重建Global索引。

       不支持对二级分区表的一级分区交换分区。

    l   清空分区

    l   清空分区会使得Global索引失效,可以通过UPDATE GLOBAL INDEX子句来同步更新Global索引,或者用户自行重建Global索引。

    l   分割分区

       分割后的新分区,可以与源分区名字相同,比如将分区p1分割为p1,p2。但数据库不会将分割前后相同名的分区视为同一个分区,这会影响分割期间数据库对源分区查询行为的判断。

       不支持分割HASH分区。

       若指定分割点分割RANGE分区,分割点要位于正被分割分区的分区键范围内。

        若不指定分割点分割RANGE分区,分割后的新分区必须满足分区范围定义递增的约束。

        若指定分割点分割LIST分区,分割点必须是源分区的一个非空真子集。

       若不指定分割点分割LIST分区,分割后的每个新分区都必须是源分区的一个非空真子集,且互不交叉。

       分割分区会使得Global索引失效,可以通过UPDATE GLOBAL INDEX子句来同步更新Global索引,或者用户自行重建Global索引。

       不支持对二级分区表的一级分区分割分区。

    l   合并分区

       对于RANGE/INTERVAL分区,源分区的范围要求连续且递增。

        合并后的新分区,对于RANGE/INTERVAL分区,可以与最后一个源分区名字相同;对于LIST分区,可以与任一源分区名字相同。

       如果新分区与源分区名字相同,数据库会将新分区视为对源分区的继承,这会影响合并期间数据库对源分区查询行为的判断。

       不支持合并HASH分区。

       合并分区会使得Global索引失效,可以通过UPDATE GLOBAL INDEX子句来同步更新Global索引,或者用户自行重建Global索引。

       不支持对二级分区表的一级分区合并分区。

       USTORE存储引擎表不支持在事务块/存储过程中执行合并分区的操作。

    6.4 分区表业务锁问题

    • 故障现象

    l   使用分区表业务,出现等锁超时。

    l   使用分区表业务,出现死锁。

    • 故障原因

    可能原因有以下:

    l   事务块/存储过程中同时进行分区DDLDML等混合业务

    l   在一个分区DDL命令中执行多个DDL操作

    l   进行合并分区等同时作用多个源分区的操作

    • 处理方法

                                    步骤 1     在在《特性指南》中“分区表 > 分区表并发控制”章节给出了分区表表锁、分区锁的设计规格,可以参考对应资料部分进行定位和排查。

                                    步骤 2     判断是否在事务块/存储过程中同时进行分区DDLDML等混合业务。

                                    步骤 3     事务块/存储过程中执行的混合业务,会依次给业务作用的表、分区加上对应业务级别的锁。如果业务设计不合理,可能会导致互锁,出现死锁场景;如果在事务块/存储过程中同时进行分区DDLDML等混合业务,会持有目标分区锁直到事务/存储过程结束,其他并发业务会持续等锁,可能出现锁超时场景。

                                    步骤 4     下面的业务就会出现死锁场景:

    --业务一:
    begin;
    delete from t1 partition (p1);
    alter table t1 truncate partition p2 update global index;
    commit; 
    --业务二:
    begin;
    delete from t1 partition (p2);
    alter table t1 truncate partition p1 update global index;
    commit;

                                    步骤 5     下面的业务会持锁到事务结束:

    begin;
    alter table t1 truncate partition p2 update global index;
    copy t1 from '/home/omm/test.dat';
    commit;

                                    步骤 6     判断是否在一个分区DDL命令中执行多个DDL操作。

                                    步骤 7     如果在一个分区DDL命令中先后对不同分区进行DDL操作,会依次给不同分区加写锁。如果并发的DQL/DML恰好作用在这些分区,且顺序相反,就可能出现死锁场景。

                                    步骤 8     下面的并发业务就会出现死锁场景:

    --业务一:
    alter table t1 truncate partition p2, truncate partition p1;
    --业务二:
    delete from t1; --先扫描p1,再扫描p2

                                    步骤 9     判断是否进行了合并分区等同时作用多个源分区的操作。

                                 步骤 10     这种场景下的死锁原因与上面场景是一样,也是由于串行加锁导致的。

                                 步骤 11     业务原因导致的锁超时/死锁,需要分析业务模型,对业务进行改造。

    ----结束

    7.安全常见问题

    7.1 SSL证书过期

    • 故障现象

    l   服务端SSL证书过期打印告警日志:The server certificate will expire in xx days

    l   客户端SSL证书过期打印告警日志:The client certificate will expire in xx days

    • 故障原因

    SSL证书过期。

    • 处理方法

    重新生成新的SSL证书,设置合理的有效性范围,并替换服务端和客户端证书。用户可以通过打开证书文件查看证书有效期的起止时间。操作步骤请联系管理员处理。

    【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
    • 点赞
    • 收藏
    • 关注作者

    评论(0

    0/1000
    抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。