SQL Server资源控制器详解:资源池、工作负载组与分类器 简介面向 SQL Server 数据库管理员、运维人员与解决方案供应商的一份技术文档聚焦 SQL Server 2008 R2 在 CPU 和内存资源分配上的最大优化方式。文档从 SQL Server 2005 的独立实例与处理器亲和度方案讲起指出资源静态分配无法共享的问题也提到虚拟化方案因额外开销而并非最优解进而引出 SQL Server 2008 R2 新增的资源控制器机制。通过介绍资源池、工作负载组、CPU 与内存最小值和最大值等核心概念说明如何为不同数据库请求动态分配资源兼顾负载隔离与整体利用率。压缩包共 1 个文件为 docx 文档大小约 84KB方便直接阅读和标记重点。目前已有 1353 人学习内容兼具原理分析与配置思路尤其适合需要改善多数据库实例资源争抢、优化查询与写入负载分配的数据库管理员和解决方案供应商参考。1. 资源控制器为什么 SQL Server 2008 R2 推翻了「亲和度隔离」这个老办法把 CPU 和内存的最大优化分配做到位SQL Server 2008 R2 靠的不是把资源「焊死」给某个数据库而是一套叫资源控制器Resource Governor的调度层。2005 时代应对多数据库抢资源的主流做法是用处理器亲和度把实例隔开——每个库一个实例资源一旦绑过去就再也借不出去重负载库打满 CPU 时旁边空闲实例只能干看着。2008 R2 用「资源池 工作负载组 分类器函数」翻过这一页池与池之间资源可以流动、可以借同时用最小值保证每个池的下限。代价是配置必须靠写脚本不是点几下鼠标就能搞定。这篇要把三件事讲透池的 Min/Max 是什么语义、请求怎么被分类进正确的池、为什么这套机制被设计成必须脚本化。2. 资源池的 Min/Max百分比背后是「保留」与「封顶」两套独立语义资源池是资源控制器里的第一层抽象官方定义是「服务器资源的集合这些资源在池内对工作组是可用的」。听起来简单但理解池的关键在于它既不像磁盘分区那样把硬件物理划走也不只是简单的配额记账。池的全部秘密都围绕 Min 和 Max 两组百分比展开而这两组百分比的语义完全不同很多误配事故都源于把它们当成「分配」和「上限」这么简单的两个词。2.1 两个默认池系统池与默认池的分工默认环境下服务器上只存在两个池。系统资源池承载 SQL Server 引擎自身的内部负载比如惰性写入器、死锁检测、查询通知这类后台任务默认资源池是所有没有被分类器特殊指定的请求的落脚点。这两个池的初始配置都是 Min0、Max100等于没做任何限制——所以不碰资源控制器时2008 R2 的行为跟 2005 几乎没有差别所有请求照旧自由竞争。系统池存在的意义容易被忽略。任何资源治理方案都必须先把「引擎自用」和「用户负载」分开否则你给某个业务池设了 30% 的 CPU 下限结果系统任务也在抢这 30%保障就失效了。所以系统池的参数锁定不接受修改默认池作为兜底池调整它的 Max 等于给所有漏网请求设总闸门。新建池后池本身不直接接收请求请求先进的是工作负载组组再绑定到池。「池」管的是资源额度「组」管的是池内的行为策略。这张拓扑在 SSMS 里看得很直观池在里层组挂在池下每个默认池都挂着一个同名的工作负载组。2.2 Min 值不是「分配」而是「保留」总和不能超过 100% 的约束从哪来Min 的官方语义是这部分资源专门分配给这个池对池而言始终可用。它解决的是 2005 亲和度方案里的死结——资源一旦绑定就不可流动。Min 没有把资源划死它只承诺「这个池需要时总有这么多资源可拿」。举例来说OLTP 池设了 30% 的 CPU Min凌晨没有 OLTP 流量时这 30% 的执行时间不会空转浪费会被调度器让给其他有需求的池一旦白天 OLTP 流量上来调度器会优先把这 30% 的执行份额还给 OLTP 池。所以「所有池 Min 总和不能超过 100%」这个约束的本质是担保一致性如果两个池各要 70% 的 Min那它们同时满载时系统根本拿不出 140% 的 CPU 来兑现承诺。官方把规则定为 100% 是一个数学上必然的选择。剩下没有声明 Min 的资源不属于任何池谁有需求谁去抢这是池之间「弹性」的来源。内存的 Min 同理但实现细节不同。SQL Server 的内存管理器以缓冲池为单位记账每个池在缓冲池里有一块「担保额度」。池的实际用量低于担保额度时富余部分可以被其他池借用——但借出的前提是借入方自己的用量还没超过它的 Max。一旦担保方开始需要内存回收动作是即时强制的因为内存页的调度比 CPU 时间片更直接。我一般会这么理解CPU 的 Min 靠调度权重实现内存的 Min 靠缓冲池账本实现它们都不是物理隔离。2.3 Max 值为什么允许短暂打满 100% 也视为正常Max 的直观理解是「不得超过的上限」但在 CPU 上它是一个软上限。官方文档里明确说过即使设置了 Max池也可能触发短暂的 CPU 百分之百高峰这些高峰不视为问题。原因在于 CPU 的配额是调度器按时间窗统计的SQL Server 在每个逻辑 CPU 上都有对应的内部调度器按池的当前权重分配执行份额权重会被 Max 压低但压低是逐渐收敛的过程统计窗口之内出现突发脉冲是被允许的。这跟内存的 Max 有本质差别。缓冲池的分配是逐页记账一个池的内存用量达到 Max 之后新的内存授予请求只能排队等待不存在「短暂超一点」的说法超了就是超了要么等池内释放要么走借用机制还回去。把这个差别说清楚是为了解释两类翻车现场CPU 设太小通常是「变慢」内存设太小通常是「直接卡死在内存授予等待上」。2008 R2 在软上限之外补充了 CAP_CPU_PERCENT 这个硬上限参数把「建议别超过」和「绝对不许超过」分开。两者比较更直观参数上限性质行为表现MAX_CPU_PERCENT软上限短时间窗口内允许脉冲超过调度器在更长窗口内拉回均值CAP_CPU_PERCENT硬上限任何时间窗都不超过该值适合对池做契约式封顶MAX_MEMORY_PERCENT硬上限缓冲池逐页记账池用量到达上限后新请求排队用 CAP_CPU_PERCENT 的场景通常是多个业务共存的服务器Max 保证「大多数时候不会抢过头」CAP 保证「任何时刻都不会抢过头」。而 Min 加 Max 的组合加上它们之间的弹性区间就是 2008 R2 对「最大优化分配」给出的最终答案——不是把整台服务器的资源一次性分光而是把「保障」和「封顶」做成互相独立的两个旋钮。3. 工作负载组与分类器请求凭什么被分到正确的资源池有了池下一个关键问题是路由。请求不会自动知道该进哪个池需要一段代码在会话建立时做判断并把会话指派到某个工作负载组。这是 2008 R2 资源控制器里最「黑匣子」的部分——能看到结论不容易看到它什么时候执行、依据什么判断。而官方文档给出的答案恰恰是分类器函数一个返回组名的普通 T-SQL 标量函数。3.1 工作负载组池内资源行为的真正消费单位工作负载组是池内的策略容器组上挂着一组行为参数决定池内每一个请求能以多大力度使用资源。光有池只能做总额限制做不了池内优先级区分而组补上了这一层。生产里最常见的用法是同一个池里挂两个组一组给核心交易一组给后台报表两组共享同一个池的额度但内部优先级不同。核心参数如下参数默认值作用IMPORTANCEMEDIUM池内相对优先级LOW / MEDIUM / HIGH只在同池组之间比较REQUEST_MAX_MEMORY_GRANT_PERCENT25单个请求可从池获得的「内存授予」占缓冲池的百分比REQUEST_MAX_CPU_TIME_SEC0单个请求可消耗 CPU 时间的上限秒0 表示不限制REQUEST_MEMORY_GRANT_TIMEOUT_SEC0请求等待内存授予的超时0 表示使用内部默认策略MAX_DOP0组内请求的最大并行度0 表示沿用实例配置注意 IMPORTANCE 的作用域是池内。跨池比较没有意义因为两个池的资源额度已经被 Min/Max 定义过了IMPORTANCE 只在同一个池里的多个组抢同一份额度时有说服力。这个设计很克制池管账组管调度。REQUEST_MAX_MEMORY_GRANT_PERCENT 是最容易误伤的参数。它限制的不是查询运行时的缓冲池占用而是「内存授予」——排序、哈希连接这类操作在开始执行前向内存管理器申请的一块专用内存。如果设得太小大排序会被拒绝执行或排队等待整个查询卡在等待类型 RESOURCE_SEMAPHORE 上。默认 25 已经是官方权衡过的数值调低前要确认你的查询真的不需要大内存授予。3.2 分类器函数一段返回 SYSNAME 的 T-SQL 决定会话归属分类器函数就是一段带 SCHEMABINDING 的标量函数。它不接收显式参数读取的是当前会话的上下文属性输出是一个字符串——目标工作负载组的名字。判断依据包括登录名、应用程序名、主机名、数据库名这些连接级信息。之所以是「会话级」而不是「语句级」是因为分类器只在连接建立时执行一次。USE master; GO CREATE FUNCTION dbo.RG_Classifier() RETURNS SYSNAME WITH SCHEMABINDING AS BEGIN DECLARE GroupName SYSNAME Ndefault; -- 按应用程序名分流OLTP 应用进 OLTP 组 IF (APP_NAME() LIKE N%OLTP%) SET GroupName NOLTPGroup; -- 按登录名分流报表专用账号进报表组 IF (SUSER_SNAME() Nreport_sa) SET GroupName NReportGroup; RETURN GroupName; END; GO这段函数本身不执行任何资源分配它只负责「贴标签」。真正让它生效的是后续的绑定步骤通过 ALTER RESOURCE GOVERNOR 把函数挂到服务器级并执行 RECONFIGURE。绑定之后每个新建会话在登录握手阶段都会调用一次这个函数用返回值决定该会话进入哪个工作负载组进而决定它使用哪个资源池的额度。需要澄清一个常见误解分类器不是每条 SQL 执行时重新判定的。会话一旦建立组归属就固定了直到连接断开。这意味着如果你改了分类器逻辑已经存在的连接不会自动迁移到新组。这也是后面避坑章节里连接池问题的根源。3.3 一次完整的配置流程从建池到生效把池、组、分类器串起来一次完整的配置是四步走建池建组建函数绑定并重配置。脚本顺序不能颠倒因为组绑定池时池必须已存在分类器引用的组也必须在绑定前建好。-- 第一步建资源池定义 CPU 与内存的 Min / Max CREATE RESOURCE POOL [OLTPPool] WITH ( MIN_CPU_PERCENT 20, MAX_CPU_PERCENT 70, CAP_CPU_PERCENT 70, MIN_MEMORY_PERCENT 10, MAX_MEMORY_PERCENT 40 ); GO CREATE RESOURCE POOL [ReportPool] WITH ( MIN_CPU_PERCENT 0, MAX_CPU_PERCENT 30, MIN_MEMORY_PERCENT 0, MAX_MEMORY_PERCENT 30 ); GO -- 第二步建工作负载组并绑定到池 CREATE WORKLOAD GROUP [OLTPGroup] WITH ( IMPORTANCE HIGH, REQUEST_MAX_MEMORY_GRANT_PERCENT 20 ) USING [OLTPPool]; GO CREATE WORKLOAD GROUP [ReportGroup] WITH ( IMPORTANCE LOW, REQUEST_MAX_MEMORY_GRANT_PERCENT 10 ) USING [ReportPool]; GO -- 第三步建分类器函数见上一节 USE master; GO CREATE FUNCTION dbo.RG_Classifier() RETURNS SYSNAME WITH SCHEMABINDING AS BEGIN DECLARE GroupName SYSNAME Ndefault; IF (APP_NAME() LIKE N%OLTP%) SET GroupName NOLTPGroup; IF (SUSER_SNAME() Nreport_sa) SET GroupName NReportGroup; RETURN GroupName; END; GO -- 第四步绑定分类器并让配置生效 ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION [master].[dbo].[RG_Classifier]); GO ALTER RESOURCE GOVERNOR RECONFIGURE; GO这段脚本里OLTPPool 设了 20% 的 CPU Min 和 10% 的内存 MinReportPool 的 Min 都是 0。含义是OLTP 池在最忙时也有 20% 的 CPU 保底报表池则完全依赖闲时资源。两个池的 Max 之和是 100%这意味着二者在极端情况下可以打满整台机器但因为 CPU Max 是软上限实际运行中两个池会按各自权重收敛不会出现需要硬切换的开关动作。脚本最后的两条语句是最容易被跳过的。ALTER RESOURCE GOVERNOR 负责把分类器函数写入运行时的资源控制器元数据RECONFIGURE 则把刚刚建好的池、组配置全部加载到运行中的服务器。缺少任何一条前面所有 DDL 都只是躺在系统表里的定义不会产生任何约束效果。这也是后面避坑章节要展开的经典「改了没生效」的根源。4. 验证与观测怎么确认请求真的进了对的池资源控制器的配置过程是「写脚本-提交-重配置」但脚本提交成功不等于策略按预期运转。最典型的情况是分类器函数逻辑写反了或者有拼写错误请求静默落进 default 池——没有任何报错只是资源分配结果和你设计的不一样。所以要给整套机制配一套验证手段用 DMV 和等待类型把黑匣子变成可观测的账本。4.1 用 DMV 看池和组的实时用量SQL Server 为资源控制器暴露了一组动态管理视图分别对应配置态和运行态。配置态视图说明「你设置了什么」运行态视图说明「实际发生了什么」。实操中主看三个资源池运行时统计、工作负载组运行时统计、当前会话归属。-- 资源池实际用量CPU 毫秒数与内存 MB 数 SELECT name, min_cpu_percent, max_cpu_percent, cap_cpu_percent, min_memory_percent, max_memory_percent, cpu_usage_ms, memory_usage_mb FROM sys.dm_resource_governor_resource_pools; GO -- 工作负载组累计用量CPU 时间与内存授予事件 SELECT g.name, g.pool_id, g.total_cpu_usage_ms, g.total_mem_grant_events_count, g.request_max_memory_grant_percent, g.max_dop FROM sys.dm_resource_governor_workload_groups AS g; GO -- 当前每个会话分到了哪个组、哪个池 SELECT s.session_id, s.login_name, s.program_name, g.name AS workload_group, p.name AS resource_pool FROM sys.dm_exec_sessions AS s LEFT JOIN sys.dm_resource_governor_workload_groups AS g ON s.group_id g.group_id LEFT JOIN sys.dm_resource_governor_resource_pools AS p ON g.pool_id p.pool_id WHERE s.is_user_process 1; GO这三条查询回答三个问题池的资源有没有按预期被消耗、各组累计吃了多少资源、某个具体连接进的是哪个组。第三个查询在排查「分类器有没有生效」时最有力——如果某个 program_name 为 OLTP 的会话没有出现在 OLTPGroup 下那问题一定出在分类器或连接复用上而不是资源池配置上。运行时统计是累计值不是瞬时值。cpu_usage_ms 从 SQL Server 实例启动或配置重载后开始累计所以做对比测试时先看一眼累计值执行测试语句后再次查询用差值判断该池是否真正承担了这个请求的 CPU 消耗。拿差值而不是绝对值做结论是这套观测方法里最容易踩的细节。4.2 等待类型与性能计数器怎么发现「资源被池卡住」池限制生效时查询通常不会报错而是表现为等待变长。与资源控制器最相关的等待类型是 RESOURCE_SEMAPHORE——它表示查询在等待获得内存授予。当池的 MAX_MEMORY_PERCENT 被调小或者组上的 REQUEST_MAX_MEMORY_GRANT_PERCENT 太低大排序、大哈希连接就容易被堵在这个等待上表现是查询「卡住了」但 CPU 和 IO 都很闲。SELECT wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms FROM sys.dm_os_wait_stats WHERE wait_type NRESOURCE_SEMAPHORE; GO这个视图统计的是实例级别的累计等待。如果 wait_time_ms 相对基线显著增长优先怀疑资源池或工作负载组的内存授予参数。注意 RESOURCE_SEMAPHORE 不等于资源池的 Max 内存限制本身——它也可能由服务器整体内存压力引起。区分方法是用 4.1 的会话归属查询确认目标请求所在的池再看该池的 memory_usage_mb 是否已经逼近 max_memory_percent 对应的值或池的最大内存估算值。除了 DMV性能监视器里还有 SQLServer:Resource Governor Stats 性能对象包含按池统计的 CPU 使用率、内存使用率等计数器。它在压测时比 DMV 更直观能画出时间序列曲线适合观察 CPU 的脉冲是否被 Max 收敛到目标区间。不过计数器和 DMV 的统计口径有差异不要期望两边数值完全一致看趋势即可。4.3 最小/最大参数的效果怎么验证验证 Min/Max 是否真的按预期工作最可靠的办法是构造一个「资源占满」的场景。常见做法是开两个查询会话分别落到两个不同的池然后同时跑同一张大表的聚合查询观察 4.1 里两个池的 cpu_usage_ms 增量比例。如果 OLTP 池设了 20% 的 Min在两者同时打满 CPU 时OLTP 池的 CPU 份额应明显高于没有 Min 的报表池且最终稳定在接近 Min 的比例附近。反向验证 Max 更简单把某个池的 MAX_CPU_PERCENT 临时调到一个很低的值比如 5跑一个原本需要大量 CPU 的查询观察它的执行时间变长多少。如果变化不明显说明分类器没生效或者查询没进这个池如果明显变慢说明 Max 的软上限机制在工作。验证完记得改回原值并再次 RECONFIGURE。内存侧的验证要小心不要直接用大查询去测内存上限容易把池内存耗尽触发页回收影响同池其他请求。建议先用 4.1 的第二个查询观察基线的 memory_usage_mb再逐步调整 Max 看阈值变化。资源控制器的内存机制有「借出再回收」的滞后观察时给系统留出缓冲时间不要盯着秒级数字下结论。5. 资源控制器避坑指南五个容易翻车的配置现场资源控制器这套机制的坑集中在「生效时机」和「参数语义」两类。生效时机相关的坑会让配置看起来没生效参数语义相关的坑会把查询卡死。下面五条是实践中最常遇到的踩坑场景按现象、原因、解决三步拆开。5.1 分类器不生效改了等于没改现象一改完分类器函数后新开的连接仍然全部落在 default 池设置的限制完全没体现。原因最常见的是漏掉了 ALTER RESOURCE GOVERNOR RECONFIGURE。分类器函数体的修改不会自动加载到运行中的服务器资源控制器维护的是一份运行态元数据函数定义改了运行态没有同步新连接依然按旧函数分类。其次要检查分类器是否真的返回了目标组名——函数里写了 IF 分支但条件里的属性值跟实际连接的特征对不上比如 APP_NAME() 的大小写或空值返回结果是 NULL 时会话会静默落入 default 组没有任何错误提示。解决改完函数后依次执行 ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION [master].[dbo].[RG_Classifier]) 和 ALTER RESOURCE GOVERNOR RECONFIGURE然后用 4.1 的会话归属查询确认新建连接的组归属。如果只是改函数内部逻辑理论上 RECONFIGURE 即可但重复执行一次 WITH 再 RECONFIGURE 更保险。排查时先看 SUSER_SNAME()、APP_NAME() 的实际返回值再跟函数里的判断条件逐一比对不要凭印象写条件。现象二应用侧连接池里的存量连接不迁移代码发版后老连接还在旧的组新逻辑迟迟不生效。原因分类器只在会话建立时执行一次。连接池复用的连接没有重新登录握手就不会重新分类。这不是 bug是设计如此——避免每条 SQL 都付出一次分类开销。代价是策略调整对存量连接有滞后。解决对连接池类的应用最干净的办法是让应用侧重启连接池数据库侧手段是 KILL 掉目标 session 强制其重连。如果是发布窗口内不允许中断连接可以把新策略的生效时间点设计成与应用发布窗口对齐避免出现「应用连的是新库、会话还在旧组」的中间态。5.2 资源配置被拒Min 总和与 Max 内存的硬约束现象三调整某个池的 MIN_CPU_PERCENT从 20 改成 80RECONFIGURE 直接报错配置没有生效。原因所有池的 Min 总和不能超过 100%。这是一条硬校验在 RECONFIGURE 阶段执行。常见的触发场景是把新池的 Min 调高却忘了已经有一个池的 Min 是 30两个池加在一起超过 100整个配置被拒绝——注意是被「整体拒绝」不是只拒绝新值。解决把 Min 当成「有承诺的预留额度」来规划所有池的 Min 之和留出余量比如总和不高于 70剩下的留给自由竞争区间。修改顺序上先调低已有池的 Min再调高新池的 Min避免中间状态超限。变更时把 RECONFIGURE 当作事务性操作对待报错后先查所有池的 Min 总和再动手。现象四把池的 MAX_MEMORY_PERCENT 调低后池内查询开始频繁出现 RESOURCE_SEMAPHORE 等待部分查询超时。原因内存 Max 是硬上限且内存授予在查询开始前就要申请。Max 调低意味着池内可用于内存授予的空间变小而查询的授予需求不变两者冲突就排队。更隐蔽的是REQUEST_MAX_MEMORY_GRANT_PERCENT 默认 25 是相对「池」的缓冲池空间不是相对整机内存——池的 Max 缩小时这个 25% 的绝对量也跟着缩小连锁反应被成倍放大。解决调低内存 Max 时同步检查组上的 REQUEST_MAX_MEMORY_GRANT_PERCENT确保「池的 Max × 组的内存授予百分比」仍能满足目标查询的授予需求。观测手段回到 4.2 的 RESOURCE_SEMAPHORE等待显著增长就说明授予空间不足要么调回 Max要么给大查询单独建一个内存授予百分比更高的组。5.3 验证与回归配置改对还不够现象五用 DMV 做对照测试发现并发压测时池的 cpu_usage_ms 增长比例跟 Min 设置对不上怀疑机制失效。原因cpu_usage_ms 是累计值且 Min 的保障是「该池有需求时的调度优先级」不是物理带宽。两个池的查询并发程度不同单次测试得到的增量比例受查询本身消耗差异影响很大。把 Min 理解成「调度权重倾斜」而不是「按比例切分 CPU」就不会被短期数字误导。解决做 A/B 对照时保持两侧查询类型、并发数、数据量一致多跑几轮取累计增量的均值。观察窗口拉长到分钟级避开冷缓存、编译开销等干扰。还有一个兜底做法把池的 Max 临时设成与 Min 相同MaxMin此时池的行为会逼近 2005 亲和度的「硬隔离」效果可以验证 Min 的绝对保障是否到位验证完再改回。6. 为什么官方宁可让你写脚本分类器设计背后的取舍资源控制器整套机制里最难上手的一点也是最值得回味的一点池和组的参数明明是结构化的为什么官方不做一个完整的图形化配置界面非要把分类器留成写函数6.1 图形界面表达不了「决策逻辑」池和组的配置本质上是一组键值对适合用表格呈现但分类器是一个决策逻辑。判断依据可以是登录名、应用名、主机名、数据库名的任意组合可以加 AND/OR、可以按前缀模糊匹配、甚至可以按时间段区分繁忙策略。这些如果用图形界面表达要么做成一套连官方自己都不满意的表达式构建器要么退化成「只能按单一属性分配」把机制的灵活性砍掉大半。官方最终选择了「引擎负责执行策略策略由 DBA 用 T-SQL 表达」的分层把复杂留给了愿意写脚本的人把简单留给了引擎内部。6.2 池与组的抽象如何演化为资源治理的通用模型这套「池组分类器」的模型在 2008 R2 之后被后续版本延续池和组的核心概念没有推翻只是在上层增加了更多维度的治理能力。它的通用性来自一个关键设计把「资源额度」和「资源行为」拆成两层。额度归池管行为归组管分类器负责把请求映射到组。三层各管一件事替换任何一层都不影响另外两层。对比虚拟化隔离方案资源控制器没有额外的操作系统开销治理发生在引擎内部资源粒度可以细到单个工作负载组。这个拆法本身比具体参数更值得借鉴——你可以在自己的系统设计里复用同样的分层账户管配额策略管行为路由管映射。最后保留一个个人习惯我给某套多数据库环境做资源整治时第一次就在漏掉 RECONFIGURE 上翻了车配置写了半天线上行为纹丝不动排查了两小时才发现运行态元数据根本没同步。从那以后我每次改池和组配置都强制走一遍「DDL → RECONFIGURE → 查 DMV 核对」的闭环先小流量验证再全量生效。这套流程不值得抄近路。这份整理好的配置脚本与说明可以直接拿去用希望帮到你。本文还有配套的精品资源点击获取