SAP HANA负载管理,说到底就是给内存数据库里的SQL请求做分类、排队、设限、调优先级的一套系统化机制。我刚接手那套混合负载HANA时,最头疼的不是SQL写得差,而是核心交易和月底报表挤在同一套库里,一到批量跑数,核心查询直接从几毫秒飙到几秒,业务电话一个接一个。后来把Workload Management这套东西从头到尾啃透、用起来,才算把局面控制住。
这篇文章不打算复述官方手册,而是把我自己配置负载管理的过程、踩过的坑、以及后来摸索出来的稳妥参数,原原本本整理出来。内容主要面向正在维护SAP HANA的BASIS或DBA,也适合刚入门、对Workload Class、资源池、语句级限制这些概念还比较发懵的朋友。不管你之前有没有系统学过负载管理,按这篇文章的思路走一遍,至少能让你在混合负载场景下不再手足无措。
1. 先搞清楚:SAP HANA负载管理到底在管什么
1.1 三个层次:语句级、会话级、系统级
很多新手一上来就查“怎么创建Workload Class”,却搞不清自己到底要管什么。我习惯把HANA的负载管理拆成三个层次来看,这样配置的时候思路会清楚很多。
语句级:针对单条SQL的约束,比如一条查询最多能用多少CPU时间、最多能分配多少内存、最长允许跑多久。这是最细粒度的管控,也是日常用得最多的。会话级:限制一个数据库连接同时能有多少条活动语句,防止某个应用连接池一下子把几十个重型报表全怼进来。系统级:是整个HANA实例层面的资源分配,包括全局内存分配上限、资源池的CPU份额和内存限额,决定所有SQL共享的“总盘子”有多大。
打个比方:语句级限制是约束单个快递员一天最多跑多少单;会话级限制是约束一个快递站点同时最多处理多少包裹;系统级限制则是分拨中心的总吞吐能力。只卡单条SQL,不卡总并发,高峰期一样会被打爆;反过来只卡全局,不控制单条语句,一条失控查询就能把整个实例拖垮。所以配置负载管理,三层都要有,缺一不可。
1.2 一条SQL进来之后,到底经历了什么
理解负载管理,关键是先理解一条SQL从进入到执行,在HANA内部要“过几道门”。我个人摸索出来的执行链大致是这样:
客户端发起SQL请求后,HANA先做连接认证,收集当前会话的上下文信息,比如用户名、客户端ID、应用名、Schema、语句类型。接着进入工作负载映射阶段,系统拿这些信息去匹配已经定义好的Workload Mapping规则。命中哪条映射,SQL就被归到对应的Workload Class里。然后这个Class会告诉HANA:你属于哪个资源池、优先级是多少、有哪些限制条件。最后,SQL带着这些“身份标签”进入执行队列,如果是并发超限,就排队等待;执行过程中如果触发了CPU或内存阈值,就可能被强制终止。
这个顺序特别重要,因为很多人配置的时候会搞反。我见过有人先创建映射,再去创建Class,结果映射指向一个不存在的类,直接报错。正确顺序永远是:先有资源池,再有Workload Class,最后才建映射。映射只是“指路牌”,指向的类必须已经存在。
1.3 设计负载管理方案前,先问自己四个问题
配置之前,一定要先想清楚业务诉求,而不是急着敲SQL。我每次给新环境做负载管理,都会问自己四个问题:
| 问题 | 背后含义 | 实际影响 |
|---|---|---|
| 谁是最不能慢的业务? | 核心交易、实时查询 | 要给高优先级或独立资源池 |
| 哪些作业可以“受委屈”? | 批处理、报表、数据导出 | 可以降低优先级、加强限制 |
| 系统整体有多少CPU和内存可分配? | 物理资源上限 | 决定全局限制和资源池大小 |
| 你要的是“隔离”还是“兜底”? | 强隔离用池,防失控用阈值 | 决定优先配置资源池还是语句限制 |
我见过一个比较典型的反面案例:某公司把所有分析类SQL都塞进默认类,既不设映射也不建池,结果月度报表一跑,核心交易响应时间直接飙到十几秒。后来就是通过“确认关键业务、识别受控对象、规划资源池、设置语句阈值”这四步,把混合负载理顺的。这四个问题没有标准答案,但答案直接决定你后面的配置方向。
2. 工作负载分类与映射:给SQL画个圈
2.1 Workload Class到底是什么
Workload Class(工作负载类)本质上就是一组规则属性的集合。你可以把它理解成快递公司的“分拣规则”:哪些包裹走航空件、哪些走陆运件、哪些限制重量,都提前定义好。HANA内置了几个默认类,其中DEFAULT是兜底类,所有没有被任何映射匹配到的SQL最终都会进到这个类;_SYS_INTERNAL用于系统内部任务;_SYS_ABORT是优先级最高的类,专门用来终止异常语句。
创建自定义Class的语法并不复杂,关键是想清楚每个属性填什么值。我这里写一个比较典型的例子:
CREATE WORKLOAD CLASS "CLS_BATCH_CONTROL" WITH ( STATEMENT_PRIORITY = -10, CPU_THRESHOLD = 600, MAX_STATEMENT_MEMORY_LIMIT = 16384, MAX_CONCURRENT_STATEMENTS = 2 );这个类的作用很明确:把批处理类SQL的优先级调低,CPU执行时间超过600秒就终止,单条语句内存上限16GB,同一会话同时最多跑2条。后面的小节我会逐个解释这些属性取值背后的逻辑,这里先不展开。
2.2 关键属性取值的门道
创建Workload Class时,最重要的几个属性我逐一说说。STATEMENT_PRIORITY的取值范围是-20到20,默认0,数值越大优先级越高。高优先级语句在CPU调度上会优先获得执行机会,在Linux平台上甚至可以抢占低优先级语句的CPU资源,所以调优先级要谨慎,我给批处理一般只给-10,不会一上来就-20,否则容易把低优先级作业饿死。
CPU_THRESHOLD的单位是秒,指的是CPU执行时间,不是墙上时钟时间。这一点很多新手会误解。一条SQL等锁等I/O的时间不算CPU时间,真正消耗计算资源的是它在CPU上运行的那部分。我测过一个跑批作业,墙上时间跑了20分钟,但CPU时间只有3分钟,说明它大量时间在等待资源。用CPU时间做阈值,比用总执行时间更精准,不会因为系统繁忙导致误杀。
MAX_STATEMENT_MEMORY_LIMIT的单位是MB,限制单条语句能分配的最大内存。这是防止“内存黑洞”最关键的一道闸。默认值一般来说不会自动限制,所以你不设的话,一条笛卡尔积的查询理论上可以把实例内存吃干。MAX_CONCURRENT_STATEMENTS则限制一个会话中同时活动的语句数量,超过限制的SQL会排队等待,不会立刻报错。
这里插一个我常跟同事强调的原则:限制不是拍脑袋定的,要先看基线数据。比如某条批量SQL平时内存消耗在6到8GB,你把上限设成16GB是合理的;如果你上来就设2GB,业务直接失败,那不是在管控,是在制造故障。
2.3 映射规则:怎么让SQL自动进入正确的圈子
Workload Mapping是连接“SQL特征”和“Workload Class”的桥梁。可以匹配的维度包括用户、用户组、客户端ID、应用名、Schema、语句类型,甚至会话上下文变量。我常用的是应用名加客户端组合,因为同一个用户可能既跑核心业务,又跑批量报表,光匹配用户不够精准。
示例:
CREATE WORKLOAD MAPPING "MAP_BATCH_REPORT" WORKLOAD CLASS "CLS_BATCH_CONTROL" WITH (APPLICATION = 'REPORT_APP', CLIENT = '200');这条映射的含义是:来自客户端200、应用名为REPORT_APP的所有SQL,统一归入CLS_BATCH_CONTROL这个类。配置映射时有几个细节要注意:一是映射条件是AND关系,多个条件同时满足才算命中;二是映射匹配顺序有讲究,HANA一般按创建的先后顺序匹配,所以精确的映射要放在宽泛映射前面,否则容易被前面的宽规则截胡;三是修改映射后,已建立的连接里已经解析完成的语句可能不会立刻切换类,新发起的语句才会按新映射执行,这个现象容易让人误以为没生效。
3. 资源池与全局限制:把预算分到账上
3.1 资源池模型:CPU怎么分才合理
资源池(Resource Pool)是SAP HANA 2.0开始重点强化的能力,核心作用是按CPU份额隔离不同工作负载。默认情况下所有语句都在一个默认池里跑,互不干涉也互不保护。当你希望批量作业不要抢占核心交易的CPU时,就需要给它们单独画一个池子。
创建资源池的语法在不同版本和SPS级别里略有差异,但实现逻辑是一致的,核心参数是CPU_NUMBER,表示该池可以使用的逻辑CPU数量。示例:
CREATE RESOURCE POOL "POOL_BATCH" WITH (CPU_NUMBER = 8);这里有个关键知识点:资源池的CPU管理是弹性共享的,不是绝对隔离。也就是说,如果系统CPU整体不繁忙,即使某个池的语句超过了它分配的8个逻辑CPU额度,也可以继续使用空闲CPU;只有系统CPU完全饱和时,才会按池子的份额进行调度。理解这一点很重要——资源池的价值在于“高峰期按比例分配”,而不是“平时也把资源锁死”。所以规划池子大小时,要结合服务器的总CPU核数来算,别把所有池子的CPU_NUMBER加起来超过物理核数太多,否则调度声明太多,反而达不到隔离效果。
3.2 默认池的兜底作用
很多人配置了自定义池之后,就忘了默认池。这是个大坑。所有没有显式映射到自定义类的SQL,最后都会落到DEFAULT或默认池里。如果默认池资源不足或者没有设置合理限制,那些“没人管”的SQL反而成为最大的风险来源。
我常用的做法是“先保底、再优化”:先把DEFAULT类的限制设置得稍微保守一些,比如CPU_THRESHOLD给一个比正常业务高的值、MAX_STATEMENT_MEMORY_LIMIT给一个能覆盖绝大多数查询的值,确保遗漏的SQL不至于失控,然后再对重点业务和重点批处理做精细化映射。这样即使你的映射规则漏掉了一些应用,系统至少还有一层安全网。
3.3 全局内存限制:最后一道防线
光靠单条语句内存限制还不够。举一个真实场景:某系统有128GB物理内存,HANA实例分配上限是90GB。正常情况下没问题,但某天多个受控批处理的并发量超出预期,每个批处理都用到16GB内存,加起来就超过了90GB上限。这个时候如果没有全局限制,HANA会持续从操作系统申请内存,最后触发操作系统层面的内存压力,整个实例都可能受到影响。
全局内存限制通过global.ini配置文件中的[memorymanager]段设置,核心参数是GLOBAL_ALLOCATION_LIMIT,表示整个HANA实例允许分配的内存上限。我的经验算法是:物理内存减去操作系统和其他常驻进程的内存预留,再乘以一定的安全系数。比如128GB物理内存,OS预留20GB,HANA上限就设100GB左右;如果同一台机器上还跑了其他数据库或应用,预留比例要相应加大。
还有一个GLOBAL_SQL_LIMIT参数,限制所有SQL语句分配内存的总量。它比GLOBAL_ALLOCATION_LIMIT更聚焦,专门管SQL执行时的内存消耗,和语句级限制配合使用效果最好。这两层内存防线都设好之后,才能真正做到“单条语句有上限,整体消耗有封顶”。
3.4 会话并发限制:防排队也防挤爆
除了给SQL“戴帽子”,还要给会话“设门槛”。MAX_CONCURRENT_STATEMENTS就是干这个的。我见过一个客户,BI团队的工具通过一个共享账号连接HANA,一次刷新操作会同时发出几十个查询请求,每个查询都不算重,但几十个并发叠在一起,直接把CPU打满。
后来我在那个账号对应的Workload Class里设置了MAX_CONCURRENT_STATEMENTS = 5,多余的查询自动排队,系统压力立刻降下来了。这里要注意:这个限制是按会话计算的,不是按整个数据库计算的。如果你要限制某个应用整体并发,最好结合用户组或应用名做映射,让该应用的所有会话都归入同一个受控类,才能真正做到全局排队。
4. 优先级与阈值实战:拿批量作业开刀
4.1 优先级调整带来的连锁反应
优先级是我在所有负载管理参数里最谨慎调整的一个,因为它直接影响用户体验。STATEMENT_PRIORITY高低的实际效果在CPU调度上非常明显:高优先级语句会优先拿到CPU时间片,低优先级语句只能在CPU空闲时被执行;在Linux平台上,高优先级甚至可以抢占正在运行的低优先级语句。
我接手的一个系统里,数据分析师经常手动跑大查询,每次跑的时候核心交易就变慢。我先把分析师账号映射到一个低优先级类,STATEMENT_PRIORITY = -10,再把核心交易账号映射到高优先级类,STATEMENT_PRIORITY = 5。从那次调整之后,分析师跑大查询期间,核心交易基本感知不到变化。
但优先级不代表“随意拉满”。如果所有业务都设成高优先级,那高优先级就没有意义了,反而会导致调度器频繁切换,增加系统开销。我的实践原则是:高优先级只给少数真正关键的业务,低优先级给可控的批处理和报表,大部分普通业务保持默认0即可。
4.2 完整配置Demo:限制一个“夜间宽表刷新”作业
这里用一个完整案例串一遍所有组件。场景:某个“销售宽表刷新”程序,每天凌晨跑,偶尔白天也会手动补跑。老板的要求是:白天跑的时候不能影响线上交易,晚上跑也不能把内存吃光。
第一步,先建资源池,给批处理划定CPU边界:
CREATE RESOURCE POOL "POOL_BATCH" WITH (CPU_NUMBER = 8);第二步,创建Workload Class。结合这个作业的历史基线,单条语句内存峰值一般在12GB左右,CPU执行时间一般不超过10分钟,白天手动跑时并发量低,凌晨自动跑时可能有多个任务同时启动:
CREATE WORKLOAD CLASS "CLS_BATCH_CONTROL" WITH ( STATEMENT_PRIORITY = -10, CPU_THRESHOLD = 600, MAX_STATEMENT_MEMORY_LIMIT = 16384, MAX_CONCURRENT_STATEMENTS = 2 );第三步,创建映射,把宽表刷新程序产生的SQL都归入这个类。这个程序连接HANA时带了固定的应用名,就用APPLICATION维度来匹配:
CREATE WORKLOAD MAPPING "MAP_REFRESH_WIDE_TABLE" WORKLOAD CLASS "CLS_BATCH_CONTROL" WITH (APPLICATION = 'WIDE_TABLE_REFRESH');配置完成后,立即验证映射是否生效。我习惯在管理控制台或直接查M_ACTIVE_WORKLOAD视图,确认运行中的SQL确实带上了CLS_BATCH_CONTROL标签。验证通过后,先观察一周的运行统计,再微调阈值。这个方法我用了很多年,基本可以避免“一上来就把业务限制死”的惨剧。
4.3 阈值命中之后的真实表现
了解限制命中后的行为,比配置本身更重要。CPU_THRESHOLD超限时,HANA会直接终止该SQL,用户会收到类似“statement interrupted due to workload class”的错误,这通常意味着SQL真的失控了,需要进一步做SQL优化。MAX_STATEMENT_MEMORY_LIMIT超限时同样会终止SQL,错误日志里会记录实际内存分配量,方便你判断阈值是否需要调整。MAX_CONCURRENT_STATEMENTS超限时不会终止SQL,而是让新语句进入等待队列,等前面的语句结束再执行。
这里有一个容易误解的点:内存限制不是“预留内存”,而是“事后检查”。也就是说,SQL先跑,内存先分配,超过阈值之后HANA才终止它。所以阈值设得太大,失控SQL还是会消耗很多临时内存;设得太小,又容易误伤正常作业。这也是我反复强调要先看基线的原因。
5. 监控与排障:别等卡死再看日志
5.1 常用监控视图与查询语句
负载管理上线之后,监控是每天的功课。我最常用的两张视图是M_ACTIVE_WORKLOAD和M_WORKLOAD。前者看当前正在跑的语句和它们所属的工作负载类,后者看每个类的累计统计信息。
排查问题时我经常跑类似下面的查询:
SELECT WORKLOAD_CLASS_NAME, TOTAL_STATEMENT_COUNT, TOTAL_CPU_TIME, TOTAL_EXECUTION_TIME, TOTAL_MEMORY_ALLOCATED_SIZE FROM SYS.M_WORKLOAD ORDER BY TOTAL_EXECUTION_TIME DESC;如果发现某个类累计执行时间异常高,就说明映射可能过宽,把大量SQL都圈进来了,需要回去调整映射条件。想查“当前谁在排队等待”,可以用M_ACTIVE_WORKLOAD,重点关注WAIT_TIME和STATEMENT_STATE字段,等待时间长的语句往往就是资源池或并发限制导致的瓶颈。历史趋势则通过_SYS_STATISTICS里的相关视图查,可以按小时看负载变化,判断是否需要调整资源池的CPU份额。
5.2 我在实际运维中踩过的坑
这些年配置负载管理,我踩过的坑还真不少,挑几个典型的分享。
第一个坑是映射条件写得太宽。有一次我把映射条件写成按应用名匹配,但那个应用名在系统里被好几个工具共用,结果大量日常查询都被圈进了低优先级类,被限制得死死的,业务反馈“系统突然变慢”。从那以后,我的映射条件里至少要带两个维度,比如应用名加客户端,绝不用单一条件去圈一大片。
第二个坑是只设语句内存限制,没设全局内存限制。当时我以为每条SQL限了16GB就安全了,结果某天系统内存告警,排查发现是多个批处理作业并发,虽然单条没超限,但总量逼近了物理内存上限。后来老老实实把GLOBAL_ALLOCATION_LIMIT也设了,才彻底放心。
第三个坑是低优先级设得太极端。有一段时间我把批处理作业的优先级直接调成-20,结果因为白天和夜间任务混跑,批处理任务被核心业务反复抢占,本来3小时能跑完的作业硬是拖了9个小时。后来把优先级回调到-10,同时限制了核心业务的并发抢占,效果反而更好。
第四个坑是改了Workload Class却不重连会话。有几次调整完参数,测试账号一直用旧的连接发SQL,看起来新配置完全没生效。后来才意识到,一些长连接会把之前的解析结果缓存住,新语句才会应用新类。排查映射类问题时,先换个新连接再测,能省很多时间。
第五个坑比较隐蔽:负载管理替代不了SQL优化。有个批处理SQL执行了8小时,不管怎么限优先级、设阈值,业务该等还是等。后来DBA仔细分析执行计划,发现是多表关联顺序有问题,产生了中间结果集爆炸。负载管理只能兜底和隔离,它不会把一条烂SQL变快。遇到真正消耗资源离谱的SQL,该优化的还是要优化。
5.3 负载管理上线前的自检清单
每次给新环境配置完负载管理,我都会对照自检清单过一遍,这里分享出来:
| 检查项 | 检查方法 | 通过标准 |
|---|---|---|
| 映射条件是否精确 | 逐个检查映射维度组合 | 不存在单一宽泛条件圈住大量业务 |
| 阈值是否基于基线 | 对比1周运行统计 | 正常作业不会触发限制 |
| 默认类是否有保护 | 查看DEFAULT类限制 | 遗漏SQL不至于失控 |
| 全局内存限制是否设置 | 查看global.ini配置 | GLOBAL_ALLOCATION_LIMIT已合理设置 |
| 优先级使用是否克制 | 检查所有Class优先级 | 高优先级类不超过总量的1/3 |
| 是否在压测环境验证过 | 模拟高并发批处理 | 核心交易响应时间不受影响 |
| 关键应用是否带应用名 | 检查应用连接串 | 映射所需的上下文属性有值 |
这套清单看起来简单,但每次都能帮我找出配置漏洞。我强烈建议你也把这套流程固化下来,别等出了故障再手忙脚乱地查。
最后再分享一个个人经验:负载管理上线不是一次性工作,而是持续迭代的过程。我习惯每个月花一点时间看M_WORKLOAD的累计统计,结合业务变化微调阈值和映射。刚上线时宁松勿紧,先保证业务稳定,再逐步收紧受控作业的限制。等整套机制跑顺之后,你会发现不仅系统稳定了,DBA和业务之间的沟通也顺畅了很多——因为“为什么慢”终于有了可以查、可以说清的依据。