KingbaseES数据库内存管理实战:从SGA到PGA的调优之路

0 阅读17分钟

干了这么多年的数据库运维,我发现个挺有意思的事儿。很多DBA聊起SQL调优都头头是道,但一问到内存怎么配置,就开始支支吾吾了。其实说穿了,内存管理才是数据库性能的根基。这篇文章我就把KingbaseES也就是金仓数据库的内存管理掰开揉碎讲给你听,特别是那些只能手动调的内存参数,该怎么设、怎么测、怎么避开坑。

前言:为什么内存管理这么重要

先说个我自己亲身经历的事儿。去年我接手了一个政务系统的KingbaseES数据库,业务部门天天投诉,说白天高峰期查询特别慢。我登上去一看,好家伙,shared_buffers才设了128MB。这可是台配了128GB内存的服务器啊!我就问之前负责的DBA为什么这么设,他说默认值就挺好的,没动过。我当时心里就想,这不就跟买了辆好车却只挂一档开一样嘛。

KingbaseES数据库的内存管理有个特点。它不支持自动内存管理,只支持手动配置【turn0search26】。也就是说,你不能像用某些数据库那样,设个“自动管理”就完事了。都得你自己动手调,每个内存区域的大小,都得通过配置参数去直接控制【turn0search26】。

这种设计吧,有好也有坏。好处是你对内存有绝对的控制权,想怎么分配就怎么分配。坏处就是你得搞懂每个参数是干嘛的,要是不懂还瞎设,性能反而会更差。

这篇文章我就带你把KingbaseES的内存结构搞明白,然后一个参数一个参数地跟你说该怎么设,什么场景该调大,什么场景该调小。

一、KingbaseES内存结构概述

在这里插入图片描述

要搞明白内存管理,首先得知道KingbaseES的内存是怎么分块的。简单来说,内存总共分成两大块。分别是系统全局区也就是SGA,还有进程全局区也就是PGA【turn0search26】。

1.1 系统全局区(SGA)

SGA是什么呢?它是一组共享的内存结构,里面存着一个KingbaseES数据库实例的数据和控制信息【turn0search26】。说白了,SGA就是所有数据库进程共用的一块内存区域,大家都能往里面放东西,也都能从里面拿东西。

SGA主要包含三个部分:

  • 数据页面缓存:这是SGA里占比最大的一块,存的是最近从数据文件里读取的数据块,用来提高数据库的处理性能【turn0search26】。你可以把它理解成数据库的“短期记忆”,越常用的数据越放在这里,就不用每次都去磁盘上找了。
  • 日志页面缓存:用来缓存修改数据过程中生成的REDO记录【turn0search26】。这就相当于数据库的“操作日志”,先写日志再改数据,保证事务的持久性。
  • 锁缓存:存的是并发控制机制的锁信息【turn0search26】。数据库就像个管交通的,得知道谁在访问哪个表,避免大家起冲突。

1.2 进程全局区(PGA)

PGA是什么呢?它是存单个服务进程的数据和控制信息的,属于非共享的内存区域【turn0search26】。也就是说,每个数据库连接都有自己的PGA,别的连接碰不到你的这块内存。

PGA主要包含这么几个部分:

  • 临时页面缓存:在进程的私有内存里,缓存临时表的数据页面【turn0search26】。
  • 工作内存:服务器对元组做排序或者连接运算的时候,需要在PGA里缓存临时的结果集数据【turn0search26】。要是这块内存不够用的话,数据库就得转到磁盘上去生成临时文件。那速度嘛,可就慢多了。
  • 维护工作内存:做维护性操作的时候用的最大内存空间,比如VACUUM、建索引、加外键这些操作【turn0search26】。

二、为什么KingbaseES只支持手动内存管理

这可能是很多人都想问的问题。为什么就不能像某些数据库那样,设个“自动内存管理”就完事了呢?

其实吧,这跟KingbaseES的设计思路有关系。金仓数据库是面向企业层级里的关键业务做的,主打可控性和可预测性【turn0search31】。自动内存管理听起来挺好,但实际用起来有几个问题:

  1. 不可预测性:自动管理就意味着数据库自己决定内存怎么分配。今天可能这么分,明天可能就那样分了,性能表现不稳定。
  2. 调优困难:真出了性能问题,你都不知道该调哪里。毕竟内存是数据库在“自动”管着。
  3. 资源争抢:在容器化或者虚拟化的环境里,自动内存管理可能会跟宿主机的资源管理策略起冲突。

所以KingbaseES选择了手动内存管理,把控制权交到DBA手里。当然了,这对DBA的要求也就高了。你得懂每个参数的含义,才能把数据库调到最好的状态。

三、关键内存参数详解

下面就是重头戏了。KingbaseES提供了一大堆内存配置参数,我挑几个最重要的,跟你好好讲讲。

3.1 shared_buffers(数据页面缓存)

这个参数可以说是SGA里最核心的,直接决定了数据库能缓存多少数据页。

作用:在共享内存里缓存数据页面,减少磁盘的读写次数【turn0search26】。

配置建议:

  • 推荐值:操作系统总内存的25%到40%【turn0search26】
  • 上限:不建议超过操作系统总内存的80%【turn0search26】
  • 改完之后得重启数据库才能生效

实际经验: 我一般是这么设的。如果服务器是专门跑数据库的,我就设到物理内存的40%左右。比如64GB内存的服务器,我会设成24GB左右。如果是和其他应用混部的话,那就得看情况了,可能只能给到20%。

-- 查看当前值
SHOW shared_buffers;
-- 修改示例(在kingbase.conf中设置)
shared_buffers = '24GB'

踩坑经历: 我有次就把shared_buffers设得太大了,直接设了物理内存的60%。结果操作系统留给其他进程的内存不够,导致数据库进程直接被OOM Killer杀掉了。所以千万别贪心,得给操作系统留点余地。

3.2 work_mem(工作内存)

这个参数控制的是,单个排序或者哈希操作能使用的内存量。

作用:服务器做排序或者连接运算的时候,用来缓存临时的结果集数据【turn0search26】。要是空间不够的话,数据库就会转用临时文件来存这部分数据【turn0search26】。

配置建议:

  • 默认值:4MB,一般来说都偏小
  • 推荐值:根据查询的复杂度和并发度调整,通常是8MB到64MB
  • 注意啊,这是每个排序操作单独分配的内存,不是总共的

实际经验: 这个参数真不能设太大。因为它是每个排序操作都要单独分配一份的。假设你设了64MB,同时有100个排序操作在跑,那就是6.4GB的内存需求,搞不好直接把内存给吃光了。

我的做法是这样的。先看业务高峰期有多少并发查询,再估算每个查询平均有几个排序操作,最后根据剩下的内存来分配。

-- 查看当前值
SHOW work_mem;
-- 修改示例
ALTER SYSTEM SET work_mem = '32MB';

案例: 之前碰到个报表系统,白天高峰期特别慢。我查了执行计划,发现很多查询都用了外部排序,也就是写到临时文件里那种。把work_mem从默认的4MB调到32MB之后,这些查询全部变成了内存排序,响应时间直接从30秒降到了5秒。

3.3 maintenance_work_mem(维护工作内存)

这个参数控制的是,维护操作能使用的内存量,比如VACUUM、建索引这些操作。

作用:做维护性操作的时候,能用的最大内存空间,比如VACUUM、建索引、加外键这些【turn0search26】。设得大一点,可以有效提升清理和恢复数据的速度【turn0search26】。

配置建议:

  • 默认值:64MB,对大数据库来说太小了
  • 推荐值:512MB到2GB,针对大数据库的情况
  • 注意啊,这块内存只有做维护操作的时候才会用,平时不占资源

实际经验: 要是你有大表需要经常做VACUUM或者创建索引,这个参数一定要调大。我有次给一个10GB的表建索引,maintenance_work_mem只设了64MB,结果跑了半小时才完。后来调到2GB,5分钟就搞定了。

-- 查看当前值
SHOW maintenance_work_mem;
-- 修改示例
ALTER SYSTEM SET maintenance_work_mem = '2GB';

3.4 wal_buffers(日志缓冲区)

这个参数控制的是WAL日志的缓冲区大小。

作用:缓存重做日志的内容,再由日志写进程和服务进程刷写到磁盘上【turn0search26】。

配置建议:

  • 默认值:-1,也就是自动配置,是按shared_buffers的1/32来算的,但不会小于64KB,也不会大于WAL段的尺寸
  • 推荐值:一般用默认值就行,高并发写入的场景可以适当调大一点

实际经验: 这个参数一般不用动,除非你的事务修改的数据量特别大。我有次碰到一个高并发写入的场景,默认的wal_buffers太小了,导致日志写进程频繁刷盘,拖了性能的后腿。我调到16MB之后,情况就好多了。

-- 查看当前值
SHOW wal_buffers;
-- 修改示例
ALTER SYSTEM SET wal_buffers = '16MB';

3.5 temp_buffers(临时表缓存)

这个参数控制的是,每个会话为临时表分配的内存量。

作用:在进程的私有内存里,缓存临时表的数据页面【turn0search26】。

配置建议:

  • 默认值:8MB
  • 推荐值:根据临时表的使用情况调整,一般不用动

实际经验: 要是你的业务会大量用到临时表,比如复杂的报表查询,可以适当调大这个值。但要注意,这是每个会话都会分配一份的,设太大了会浪费内存。

-- 查看当前值
SHOW temp_buffers;
-- 修改示例
ALTER SYSTEM SET temp_buffers = '16MB';

四、内存配置实践

光知道单个参数还不够,关键是怎么组合着配置。下面我给你几个典型场景的配置建议。

4.1 OLTP(高并发小事务)场景

OLTP场景的特点,就是并发高、事务小、查询都比较简单。比如银行转账、电商下单这种。

内存配置建议:

  • shared_buffers:物理内存的30%,用来缓存常用的数据页
  • work_mem:8到16MB,避免每个事务占用太多内存
  • maintenance_work_mem:512MB,足够支撑日常的维护操作
  • max_connections:要设置得合理,避免把内存耗尽

示例配置,以64GB内存的服务器为例:

# 内存相关配置
shared_buffers = '18GB'
work_mem = '12MB'
maintenance_work_mem = '1GB'
effective_cache_size = '48GB'  # 告诉优化器操作系统有多少内存可用于缓存

4.2 OLAP(复杂分析查询)场景

OLAP场景的特点,是查询复杂、数据量大、并发比较低。比如数据仓库、报表系统这种。

内存配置建议:

  • shared_buffers:物理内存的40%,尽量多缓存数据页
  • work_mem:64到256MB,复杂查询需要更多的排序内存
  • maintenance_work_mem:2到4GB,支撑大表的维护操作
  • max_parallel_workers:设置得合理一点,利用多核来加速

示例配置,以128GB内存的服务器为例:

# 内存相关配置
shared_buffers = '48GB'
work_mem = '128MB'
maintenance_work_mem = '3GB'
effective_cache_size = '96GB'
max_parallel_workers = 8

4.3 混合负载场景

很多业务既有OLTP又有OLAP,这种情况是最头疼的。

内存配置建议:

  • 可以采用折中方案,或者用资源队列来控制不同类型的查询
  • 也可以考虑用读写分离集群,读和写分别配置

示例配置,以64GB内存的服务器为例:

# 内存相关配置
shared_buffers = '24GB'
work_mem = '32MB'
maintenance_work_mem = '1.5GB'
effective_cache_size = '56GB'

我的建议: 碰到这种情况,我一般会这么处理。先看看业务的高峰期是什么时候。要是OLTP和OLAP的高峰期错开的,比如白天OLTP忙、晚上OLAP跑批,那可以用一套配置,取个中间值。要是高峰期重叠的话,那就得考虑用读写分离集群了,让OLTP走主库、OLAP走备库,各管各的。

还有个办法是用资源队列。把不同的用户或者会话分到不同的队列里,给每个队列设不同的内存限制。这样就能防止OLAP查询把内存吃光,把OLTP给饿死了。

不过说实话,混合负载是最考验DBA功力的。我碰到过好多次,一开始折中配置,结果两边都不满意。最后还是上了读写分离集群,才算彻底解决问题。

五、内存监控与调优

配置完了可不代表就完事了。你得盯着内存的使用情况,看看配置合不合理。

5.1 查看内存使用情况

KingbaseES提供了一些系统视图,可以用来查看内存的使用情况:

-- 查看共享内存使用情况
SELECT name, setting, unit, context 
FROM sys_settings 
WHERE name IN ('shared_buffers', 'wal_buffers', 'work_mem', 'maintenance_work_mem');
-- 查看缓冲区命中率
SELECT sum(heap_blks_read) as heap_read, 
       sum(heap_blks_hit) as heap_hit,
       sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) as cache_hit_ratio
FROM sys_statio_user_tables;

怎么解读结果呢:

  • 缓存命中率最好保持在99%以上。要是低于95%,就说明shared_buffers可能不够用了
  • 如果heap_read的数值很大,说明很多数据不在内存里,得从磁盘读取

5.2 监控操作系统内存

数据库的内存配置不能孤立着看,还得看操作系统层面的内存使用情况:

# 查看内存使用情况
free -h
# 查看进程内存使用
top -o %MEM

重点关注这几项:

  • available内存:也就是操作系统还有多少内存能用
  • swap使用情况:要是开始用swap了,说明物理内存不够用了
  • 数据库进程的内存占用:有没有超过配置的数值

5.3 常见内存问题与解决

碰到内存问题的时候,我一般会先看看是什么类型的。下面给你说几种常见的。

问题一:缓存命中率低

这个问题的现象很直接,cache_hit_ratio低于95%了。

那为什么会出现这种情况呢?通常来说有两个原因。一个是shared_buffers设得太小,缓存装不下常用数据。另一个是工作集本来就超过了内存容量,也就是说你的业务需要频繁访问的数据总量,比物理内存还大。

怎么解决呢?我的思路是这样的。先试着增加shared_buffers的大小,但不能太贪心,不能超过物理内存的40%。要是加了还是不行,那可能就是查询本身的问题,得优化查询语句,减少全表扫描这种操作。实在不行的话,就考虑加物理内存吧。

问题二:内存不足导致OOM

这个问题更严重,数据库进程直接被系统杀掉了,日志里能看到"Out of memory"的错误。

碰到OOM,基本就是内存配置超了。可能的情况有两种。一种是shared_buffers加上work_mem乘以并发数,加起来超过了物理内存。另一种是maintenance_work_mem在执行大维护操作的时候,一下子把内存吃光了。

解决方法其实也简单。要么调小shared_buffers,要么降低work_mem或者减少并发连接数。要是维护操作经常出问题,就调小maintenance_work_mem。要是都不行,那就得加物理内存了。

我有次碰到一个客户,白天没事,一到晚上跑批就OOM。查了半天发现是夜间有个大VACUUM操作,maintenance_work_mem设得太大了。调小之后就好了。

问题三:临时文件使用过多

这个问题的现象是,sys_temp目录下生成了大量文件。

原因嘛,通常就是work_mem太小。排序和哈希操作内存不够用,只能往磁盘上写临时文件。磁盘IO一多,性能自然就下来了。

解决方法有两个方向。一个是增加work_mem的大小,让操作尽量在内存里完成。另一个是从查询本身下手,优化SQL,减少不必要的排序操作。

六、内存调优案例

给你讲个我实际处理过的案例,让你看看内存调优具体是怎么做的。

6.1 案例背景

有个电商系统,用的是KingbaseES数据库,服务器配了64GB内存。一到业务高峰期,也就是晚上8点到10点,数据库响应就特别慢。CPU使用率不高,但IO等待特别高。

6.2 问题诊断

我登上去看了下配置,当时就愣了:

shared_buffers = '128MB'
work_mem = '4MB'
maintenance_work_mem = '64MB'
effective_cache_size = '4GB'

我的天,这配置也太保守了吧。64GB内存的服务器,shared_buffers才128MB,这能用得顺畅才怪。

再查了下缓存命中率:

SELECT sum(heap_blks_read) as heap_read, 
       sum(heap_blks_hit) as heap_hit,
       sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) as cache_hit_ratio
FROM sys_statio_user_tables;

结果只有78%。也就是说,有22%的数据请求都得从磁盘读取。

这个78%是什么概念呢?打个比方,你每查100次数据,有22次都得去磁盘上找。磁盘可比内存慢多了,这就是为什么IO等待特别高的原因。

6.3 调优过程

第一步,先调整shared_buffers。

我把shared_buffers调到了16GB,也就是物理内存的25%:

shared_buffers = '16GB'

重启数据库之后,缓存命中率提升到了96%。但还有提升的空间。

第二步,调整work_mem。

业务高峰期的并发连接数大概在200左右,大部分都是简单查询,只有少数复杂的报表查询。我把work_mem调到了16MB:

work_mem = '16MB'

这样一来,复杂查询有足够的排序内存,简单查询也不会浪费太多资源。

第三步,调整maintenance_work_mem。

系统夜间有VACUUM的作业,我把maintenance_work_mem调到了2GB:

maintenance_work_mem = '2GB'

第四步,调整effective_cache_size。

这个参数是告诉优化器,操作系统有多少内存可以用来缓存数据。我设成了48GB:

effective_cache_size = '48GB'

6.4 调优效果

调优之后,同样的业务高峰期:

  • 缓存命中率从78%提升到了99.5%
  • 平均响应时间从200ms降到了80ms
  • IO等待从30%降到了5%

业务部门再也不投诉了,我也终于能睡个好觉了。

七、总结

本文深入解析KingbaseES数据库内存管理的核心机制,强调其手动配置特性对性能的关键影响。重点剖析shared_buffers、work_mem、maintenance_work_mem等核心参数的配置原则与实战经验,结合OLTP与OLAP场景给出优化建议,揭示合理内存分配对提升数据库响应速度、避免资源争抢的重要性,助力DBA精准调优,实现性能最大化。