SQL、SQLCLR 对象和有效内存利用率

Sir*_*lot 7 performance sql-server memory sql-server-2012 sql-clr performance-tuning

我们最近将我们的 ERP 系统从 IBM Universe 转换为 SQL Server。应用程序性能通常是可以接受的,但偶尔会降级到可怕的程度。

我们在具有 32 Gb RAM 的 VMWare 上的 Win Server 2012 和 SQL Server 2012 上运行数据库。SQL 最大内存设置为 27Gb。db 服务器仅托管此数据库,不执行任何其他功能。总数据库大小约为 110Gb。该应用程序有它自己的专用服务器。

供应商广泛使用 CLR 来移植代码(超过 36,000 个标量函数)。我了解单个 CLR 在应用程序 OLTP 上下文中运行正常,但由于逐行而不是基于设置的操作而尝试执行批量作业时,不能很好地扩展。很好……很酷……继续前进。

我运行了Brent Ozar 的脚本,该脚本高可用内存确定为需要查看的内容,以及每个查询的大量执行计划。供应商建议向服务器添加更多 RAM,但这让我很恼火,因为应用程序似乎没有使用现在的内存。

我感兴趣的是 SQL 的整体性能和行为。我看到一系列症状表明某些事情不正确,但我无法确定。这就像服务器拒绝运行。它决心

粗略地说,在我看来,大约 10Gb 的内存被数据库用于缓存,大约 11GB 是免费的,大约 3.5Gb 用于计划缓存,其余的我无法解释。我对一些定义有点不确定,例如免费、保留、被盗等。它们是否重复计算?

活动监视器显示:

在此处输入图片说明

当我运行此查询时:

-- what's happening inside my buffer pool?
SELECT counter_name, instance_name, mb = cntr_value/1024.0
  FROM sys.dm_os_performance_counters 
  WHERE (counter_name = N'Cursor memory usage' and instance_name <> N'_Total')
  OR (instance_name = N'' AND counter_name IN 
       (N'Connection Memory (KB)', N'Granted Workspace Memory (KB)', 
        N'Lock Memory (KB)', N'Optimizer Memory (KB)', N'Stolen Server Memory (KB)', 
        N'Log Pool Memory (KB)', N'Free Memory (KB)')
  ) ORDER BY mb DESC;
Run Code Online (Sandbox Code Playgroud)

我得到:

+--------------------------------+---------------------+----------+
|          Counter_name          |    instance_name    |    mb    |
+--------------------------------+---------------------+----------+
| Free Memory (KB)               |                     |  11,732  |
| Stolen Server Memory (KB)      |                     |   5,426  |
| Lock Memory (KB)               |                     |      59  |
| Log Pool Memory (KB)           |                     |       4  |
| Optimizer Memory (KB)          |                     |       2  |
| Connection Memory (KB)         |                     |       2  |
| Cursor memory usage            | TSQL Global Cursor  |       1  |
| Cursor memory usage            | TSQL Local Cursor   |       0  |
| Cursor memory usage            | API Cursor          |     -    |
| Granted Workspace Memory (KB)  |                     |     -    |
+--------------------------------+---------------------+----------+
Run Code Online (Sandbox Code Playgroud)

当我运行此查询时:

-- which db's are using memory and how much. 
SELECT
    (CASE WHEN ([database_id] = 32767)
        THEN N'Resource Database'
        ELSE DB_NAME ([database_id]) END) AS [DatabaseName],
    COUNT (*) * 8 / 1024 AS [MBUsed],
    SUM (CAST ([free_space_in_bytes] AS BIGINT)) / (1024 * 1024) AS [MBEmpty]
FROM sys.dm_os_buffer_descriptors
GROUP BY [database_id];
Run Code Online (Sandbox Code Playgroud)

我得到:

+-------------------+----------+---------+
|   DatabaseName    |  MBUsed  | MBEmpty |
+-------------------+----------+---------+
| ERP               |  10,764  |    626  |
| master            |       2  |    -    |
| model             |     -    |    -    |
| msdb              |      11  |      3  |
| Resource Database |      16  |      5  |
| tempdb            |      41  |     13  |
+-------------------+----------+---------+
Run Code Online (Sandbox Code Playgroud)

当我运行此查询时:

SELECT TOP (12) Type, Name, pages_kb,
       Virtual_Memory_reserved_kb, Virtual_Memory_committed_kb
FROM   sys.dm_os_memory_clerks
ORDER BY pages_kb DESC;
Run Code Online (Sandbox Code Playgroud)

我得到:

+---------------------------+-----------------------+----------+----------------------------+-----------------------------+
|           Type            |         Name          | pages_kb | Virtual_Memory_reserved_kb | Virtual_Memory_committed_kb |
+---------------------------+-----------------------+----------+----------------------------+-----------------------------+
| MEMORYCLERK_SQLBUFFERPOOL | Default               | 11224968 |                   12999744 |                      640296 |
| CACHESTORE_SQLCP          | SQL Plans             |  3519552 |                          0 |                           0 |
| CACHESTORE_CLRPROC        | ClrProcCache          |   110232 |                          0 |                           0 |
| CACHESTORE_OBJCP          | Object Plans          |   100776 |                          0 |                           0 |
| USERSTORE_DBMETADATA      | ERP_Live              |    93856 |                          0 |                           0 |
| USERSTORE_SCHEMAMGR       | SchemaMgr Store       |    87544 |                          0 |                           0 |
| CACHESTORE_PHDR           | Bound Trees           |    73464 |                          0 |                           0 |
| MEMORYCLERK_SOSNODE       | SOS_Node              |    62456 |                          0 |                           0 |
| OBJECTSTORE_LOCK_MANAGER  | Lock Manager : Node 0 |    60792 |                     131072 |                      131072 |
| MEMORYCLERK_SQLCLR        | Default               |    40992 |                    6327292 |                      429408 |
| MEMORYCLERK_SQLSTORENG    | Default               |    28472 |                       9472 |                        9472 |
| MEMORYCLERK_SQLQUERYEXEC  | Default               |    20904 |                          0 |                           0 |
+---------------------------+-----------------------+----------+----------------------------+-----------------------------+
Run Code Online (Sandbox Code Playgroud)
  1. 看来我有 11Gb 的“可用内存”。这真的可以免费使用吗?为什么 SQL 不使用它?

  2. 在我看来,我的 ERP 系统只使用了大约 10Gb 或大约 1/3 的可用内存。(只是感觉不对。)如何鼓励我的应用程序更有效地使用内存

  3. MEMORYCLERK_SQLCLR 保留了 6.03Gb 的内存。这是 CLR 的正常行为吗?他们什么时候保留内存?当它们被编译/注册/执行时?他们曾经发布过吗?这是在“空闲内存”内吗?(由 Srutzky 回答)

  4. 回复:大量的执行计划刷新缓存有帮助吗?

  5. 我可以使用任何功能来影响上述行为吗?或者我必须接受这就是应用程序的工作方式。

  6. 我如何解释服务器上实际持有或使用的内存。

其他人请求的查询

这些:

SELECT type,
       SUM(pages_kb)/1024 AS [Memory utilized in MB],
       SUM(awe_allocated_kb)/1024 AS [Memory allocated though Windows API]
FROM   sys.dm_os_memory_clerks
GROUP BY type
ORDER BY [Memory utilized in MB] DESC;

SELECT * FROM sys.dm_os_process_memory;
Run Code Online (Sandbox Code Playgroud)

返回:

+----------------------------------+-----------------------+-------------------------------------+
|               type               | Memory utilized in MB | Memory allocated though Windows API |
+----------------------------------+-----------------------+-------------------------------------+
| MEMORYCLERK_SQLBUFFERPOOL        |                  4417 |                                   0 |
| CACHESTORE_SQLCP                 |                  3437 |                                   0 |
| CACHESTORE_CLRPROC               |                   120 |                                   0 |
| USERSTORE_DBMETADATA             |                   100 |                                   0 |
| CACHESTORE_OBJCP                 |                    99 |                                   0 |
| USERSTORE_SCHEMAMGR              |                    76 |                                   0 |
| CACHESTORE_PHDR                  |                    72 |                                   0 |
| MEMORYCLERK_SOSNODE              |                    64 |                                   0 |
| OBJECTSTORE_LOCK_MANAGER         |                    59 |                                   0 |
| MEMORYCLERK_SQLCLR               |                    38 |                                   0 |
| MEMORYCLERK_SQLSTORENG           |                    26 |                                   0 |
| MEMORYCLERK_SQLQUERYEXEC         |                    14 |                                   0 |
| MEMORYCLERK_SQLGENERAL           |                    10 |                                   0 |
| OBJECTSTORE_SNI_PACKET           |                     9 |                                   0 |
| CACHESTORE_SYSTEMROWSET          |                     8 |                                   0 |
| USERSTORE_TOKENPERM              |                     7 |                                   0 |
| MEMORYCLERK_XE                   |                     6 |                                   0 |
| MEMORYCLERK_SQLLOGPOOL           |                     4 |                                   0 |
| CACHESTORE_SEHOBTCOLUMNATTRIBUTE |                     3 |                                   0 |
| MEMORYCLERK_SQLOPTIMIZER         |                     2 |                                   0 |
| MEMORYCLERK_SQLQERESERVATIONS    |                     2 |                                   0 |
| MEMORYCLERK_SQLCONNECTIONPOOL    |                     1 |                                   0 |
| OBJECTSTORE_LBSS                 |                     1 |                                   0 |
| CACHESTORE_STACKFRAMES           |                     0 |                                   0 |
| MEMORYCLERK_SQLHTTP              |                     0 |                                   0 |
+----------------------------------+-----------------------+-------------------------------------+

+---------------------------+---------------------------+----------------------------+--------------------------------+-----------------------------------+------------------------------------+------------------------------------+------------------+-------------------------------+---------------------------+-----------------------------+----------------------------+
| physical_memory_in_use_kb | large_page_allocations_kb | locked_page_allocations_kb | total_virtual_address_space_kb | virtual_address_space_reserved_kb | virtual_address_space_committed_kb | virtual_address_space_available_kb | page_fault_count | memory_utilization_percentage | available_commit_limit_kb | process_physical_memory_low | process_virtual_memory_low |
+---------------------------+---------------------------+----------------------------+--------------------------------+-----------------------------------+------------------------------------+------------------------------------+------------------+-------------------------------+---------------------------+-----------------------------+----------------------------+
|                  28571952 |                         0 |                          0 |                   137438953344 |                          77358808 |                           28786620 |                       137361594536 |       1014012259 |                            99 |                   3734268 |                           0 |                          0 |
+---------------------------+---------------------------+----------------------------+--------------------------------+-----------------------------------+------------------------------------+------------------------------------+------------------+-------------------------------+---------------------------+-----------------------------+----------------------------+
Run Code Online (Sandbox Code Playgroud)

这个:

SELECT COUNT(*) AS [NumCachedObjects],
       CONVERT(BIGINT, SUM(CONVERT(BIGINT, size_in_bytes)) / 1024.0) AS [CachedKBytes],
       ISNULL(cacheobjtype, '<-- Totally Total') AS [CacheObjType],
       ISNULL(objtype, '<-- TOTAL') AS [bytes]
FROM   sys.dm_exec_cached_plans
GROUP BY cacheobjtype, objtype WITH ROLLUP;
Run Code Online (Sandbox Code Playgroud)

返回:

+------------------+--------------+-------------------+-----------+
| NumCachedObjects | CachedKBytes |   CacheObjType    |   bytes   |
+------------------+--------------+-------------------+-----------+
|             3882 |        62112 | CLR Compiled Func | Proc      |
|             3882 |        62112 | CLR Compiled Func | <-- TOTAL |
|                3 |           24 | CLR Compiled Proc | Proc      |
|                3 |           24 | CLR Compiled Proc | <-- TOTAL |
|               50 |         4168 | Compiled Plan     | Adhoc     |
|            26911 |      3416232 | Compiled Plan     | Prepared  |
|              101 |        99584 | Compiled Plan     | Proc      |
|                5 |         1656 | Compiled Plan     | Trigger   |
|            27067 |      3521640 | Compiled Plan     | <-- TOTAL |
|               17 |          136 | Extended Proc     | Proc      |
|               17 |          136 | Extended Proc     | <-- TOTAL |
|               16 |          536 | Parse Tree        | Check     |
|                3 |           24 | Parse Tree        | Default   |
|              313 |        20632 | Parse Tree        | UsrTab    |
|              535 |        52520 | Parse Tree        | View      |
|              867 |        73712 | Parse Tree        | <-- TOTAL |
|            31836 |      3657624 | <-- Totally Total | <-- TOTAL |
+------------------+--------------+-------------------+-----------+
Run Code Online (Sandbox Code Playgroud)

这个:

SELECT * FROM sys.dm_clr_appdomains;
Run Code Online (Sandbox Code Playgroud)

返回:

+--------------------+--------------+------------------------------------------+-------------------------+-------+---------+--------------------+-----------------+---------------+-----------+----------+---------------------+-------------------------+---------------------------+--------------------+ 
| appdomain_address  | appdomain_id | appdomain_name                           | creation_time           | db_id | user_id | state              | strong_refcount | weak_refcount | cost      | value    | compatibility_level | total_processor_time_ms | total_allocated_memory_kb | survived_memory_kb | 
+--------------------+--------------+------------------------------------------+-------------------------+-------+---------+--------------------+-----------------+---------------+-----------+----------+---------------------+-------------------------+---------------------------+--------------------+ 
| 0x00000003DECEC200 | 16           | ERP       .CLRExtensionUser[runtime].111 | 2016-07-13 10:51:23.370 | 5     | 5       | E_APPDOMAIN_SHARED | 1               | 3236          | 130810392 | 11534336 | 110                 | 15                      | 112020591                 |                206 | 
+--------------------+--------------+------------------------------------------+-------------------------+-------+---------+--------------------+-----------------+---------------+-----------+----------+---------------------+-------------------------+---------------------------+--------------------+
Run Code Online (Sandbox Code Playgroud)

Sol*_*zky 8

在等待收到我在问题评论中发布的几个问题的回复时,我至少会重申我的一个问题:“您当前的统计数据如何使您怀疑 SQLCLR 的使用与性能问题有任何关系?”

从我看到的输出来看,SQLCLR 占用的内存非常少。它有 110 MB 的物理内存,用于ClrProcCache. 好的。这只是略高于什么是被拉紧Object Plans,并采取了3.36 GB的一小部分SQL Plans。是的,MEMORYCLERK_SQLCLR已经预留约6.03 GB(不6.3 -需要采取价值kb和应用value.0 / 1024 / 1024),但是这是一个)的虚拟内存,而不是身体,和b)勉强下12.40 GB的虚拟内存的一半预留缓冲池. 如果您滚动到该Virtual_Memory_committed_kb字段,您将看到MEMORYCLERK_SQLCLR仅使用了 419.34 MB 的虚拟内存。

要检查当前的 SQLCLR 内存使用情况,您应该能够运行:

SELECT * FROM sys.dm_clr_appdomains;
Run Code Online (Sandbox Code Playgroud)

并查看该survived_memory_kb字段(不是total_allocated_memory_kb字段,因为它应该是累积分配,无论已释放什么)。

尝试回答您的三个问题:

  1. 什么是“持有”我 11.46 GB 的“空闲”内存?

    为什么你怀疑任何东西都在“持有”它?您已为 SQL Server 提供 27 GB 的物理 RAM 以供使用。它会在它想要的时候使用它想要的东西。

  2. 在我看来,我的 ERP 系统只使用了大约 10Gb 或大约 1/3 的内存

    我认为这个值是一个错误的计算。你说虽然服务器有 32 GB 的物理 RAM,但你只为 SQL Server 分配了 27 GB。如果 10 GB 是实际总数,则相当于大约 37%。但这并不是实际总数。如果您查看pages_kb最终查询的字段(针对sys.dm_os_memory_clerks),您需要将所有这些行相加,结果为:15,424,008 kb。然后SELECT 15424008.0 / 1024 / 1024;我们使用了 14.71 GB 的 RAM,其中 27 GB。如果我们将分配给 SQL Server 的 27 GB RAM 减少 11.46 GB 的“空闲”内存,则剩下 15.54 GB 应该“使用”。我们看到正在使用 14.71 GB,但这是基于对TOP (12)查询执行 a以获取使用的内存量。我怀疑 0.83 GB 的差异隐藏在过滤的行中,因此删除了TOP (12)会给我们一个更接近 15.54 GB 的数字。在这种情况下,“使用”的内存量大约是允许的物理 RAM 的 58%。

  3. MEMORYCLERK_SQLCLR 保留了 6.3Gb 的内存。

    不完全是。保留了6.03 GB 的虚拟内存,而不是物理 RAM。此外,如上所述,这是保留的,而不是提交的虚拟内存。

    这是 CLR 的正常行为吗?

    我不完全确定“正常”,但我肯定看到 SQLCLR 更喜欢使用虚拟内存来存储大型集合。

    他们什么时候保留内存?当它们被编译/注册/执行时?

    您正在查看的应该是运行时内存。由于它是reserved,我猜测在某个时间点某个操作需要那么多内存,因此保留的大小会增加以容纳它。但您的查询还显示,目前仅使用了 6.03 GB 中的 419.34 MB。

    他们曾经发布过吗?

    至少在服务重启时。但可能比这更早。我已经看到它在保留空间上保留了很长时间,但我没有花太多时间检查它是否/何时被释放。

    如果您担心垃圾收集没有运行,或者没有像您希望的那样频繁运行,您可以通过创建一个包含调用GC该类的单个函数的简单程序集来手动调用它。如果您将其加载到与其他程序集相同的数据库中并确保它具有相同的所有者(即; verify via的AUTHORIZATION子句并确保匹配),那么它将使用相同的 AppDomain。CREATE ASSEMBLYSELECT * FROM sys.assemblies;principal_id

    这是在“空闲内存”内吗?

    否。“可用”内存是指SQL Server 通过“最大服务器内存”允许使用的未使用物理RAM量。6.03 GB 的保留虚拟内存位于交换文件/页面文件中。

  4. 刷新缓存会有帮助吗?

    那么,你将如何做到这一点,确切地说?如果您的意思是执行DBCC FREESYSTEMCACHE('ALL');,那么它应该卸载所有 AppDomains,尽管我不确定虚拟内存是否总是被释放。我认为尝试至少一次以查看实际效果没有任何害处。不过,我当然不会习惯它,因为系统会产生重新创建 AppDomain、加载程序集(或程序集)以及它在ClrProcCache.

  5. 我可以使用任何功能来影响上述行为吗?

    不是我所知道的。而且我认为您不会希望 SQL Server 用完所有可用内存,因为这不会为查询处理留下任何东西。

    或者我必须接受这就是应用程序的工作方式。

    我认为您不需要也不应该接受缓慢就是这样。正如您所说,您已经用纯 T-SQL 替换了几个 SQLCLR UDF,并获得了巨大的改进。这告诉我他们错误地和不恰当地使用 SQLCLR。如果他们找到或制作了一个工具来生成这些 UDF(你怎么能得到 36,000 个!?!),那么它们是否是“最佳的”是值得怀疑的,即使是单独使用也是如此。

  6. 我如何解释服务器上实际持有或使用的内存。

    您可以使用任务管理器(“详细信息”选项卡)和资源监视器(“内存”选项卡)查看此信息。查找名为“Working Set”的列,它是使用的物理RAM量,包括共享和非共享/私有。

如果由于在活动监视器中看到与 CLR 相关的等待类型而怀疑 SQLCLR 存在性能问题,请参阅我的以下 DBA.StackExchange 相关回答:SQL Server Management Studio (SSMS) 中的活动监视器中的 SQLCLR 等待类型是什么?

关于问题中的以下评论:

我了解单个 CLR 在应用程序 OLTP 上下文中运行正常,但由于逐行而不是基于设置的操作而尝试执行批量作业时,不能很好地扩展。

我不认为这是一个非常准确的理解;-)。UDF 的可伸缩性问题并不是 SQLCLR 独有的。事实上,SQLCLR 标量函数可以做一些 T-SQL UDF 不能做的事情:参与并行计划(如果IsDeterministic设置为true);T-SQL UDF 强制执行串行计划。尽管如此,对于大多数可以在 T-SQL 中完成的操作,执行内联操作(不是在 UDF 或多语句 TVF 中抽象——T-SQL 内联 TVF 很好)执行得最好。

查看缓存计划的数量以及哪些类型正在使用什么,我们可以看到大多数缓存计划是“准备好的”——将近 27,000 个——这表明它们很可能使用 ORM(例如实体框架,休眠/nHiberate 等)。这看起来确实是一个很大的数字,但是您无能为力,因为它是 ORM 的“野兽的本性”(开发人员很少看到的一个重大缺点,但是嘿,您可以通过更多内存,对吧?)。我们还看到有将近 4000 个 SQLCLR UDF(我想知道 36k UDF 中是否有任何一个是“死代码”?)。这些是可以改进的领域(由供应商,而不是由您,不幸的是),但并不表示内存有任何问题。

查看输出,sys.dm_clr_appdomains我们可以看到 AppDomain,我相信它只在获得输出前几个小时左右创建,使用的 CPU 很少,但已分配(以一种或另一种方式)累积总数112,020,591 字节(106.83 MB)。但是,仍然只分配了 206 Kb,因此这些 SQLCLR 对象没有占用它们的内存。

我有 60 多个“由于内存压力,AppDomain XXXXX 被标记为卸载”。自 2016 年 1 月以来的消息。在这里和那里发生了奇怪的事件,并且连续 3-5 天都在糟糕的一天。起初,这些发生在我们 ETL 提取运行的半夜。但最近这些都是全天传播的。

6 个月内大约有 60 条“AppDomain Unloaded due to memory pressure”消息并不完美,但也很不错。这是平均每 3 天 1。内存不足会导致这种情况每天发生多次。当运行时查询处理需要该内存时,在繁重的活动期间发生这种情况是有意义的。这让我回到了 10 GB 的“空闲内存”只有在不执行 ETL(或其他活动增加时)时才“空闲”的想法。

您可以通过在 ETL 期间运行一些测试来更清楚地了解这种情况,以查看有多少可用内存、分配给 cached_objects 的总 KB 数以及每种类型有多少缓存计划。事实上,当性能“降级到糟糕”时运行这些测试将给出可能内存不足的最佳指示。

目前我没有看到任何证据表明这是内存限制问题。它更像是糟糕的应用程序架构和滥用功能(即 SQLCLR)。很可能是由于没有更好地理解 SQL Server 和 SQLCLR,供应商做了一些正常人不会做的事情(例如 36k 标量函数!)。

供应商建议向服务器添加更多 RAM,但这让我很恼火,因为应用程序似乎没有使用现在的内存。

这个建议也让我很恼火,但出于不同的原因:他们要求你花钱做一个绝对的猜测。他们不知道更多的记忆是否会有所帮助。如果您拥有当前 10 GB 可用内存的一半,并且“使用 CLR 花费几分钟的重复查询体验减少到几秒钟甚至没有它们的亚秒响应时间”,那么内存怎么会是问题?如果我在这里不正确,也许他们可以为您提供查询和/或证据来支持这与 RAM 相关的理论。但是您已经有了非常有力的反证,即重新编写的查询需要“几秒钟甚至亚秒级的响应时间”。因此,也许供应商应该支持他们的建议并为您购买 RAM。如果没有帮助,您可以将其交还给他们。如果它确实有帮助,不应该需要它;-)。