CLR 集成体系结构 - 性能

适用于:SQL ServerAzure SQL 托管实例

本主题讨论一些可增强 Microsoft SQL Server与 Microsoft .NET Framework公共语言运行时 (CLR) 集成的性能的设计选择。

编译过程

在编译 SQL 表达式期间,当遇到对托管例程的引用时,将生成 Microsoft 中间语言 (MSIL) 存根。 此存根包含用于将例程参数从 SQL Server 封送到 CLR、调用 函数和返回结果的代码。 该“粘附”代码基于参数类型和参数方向(向内、向外或引用)。

粘附代码可实现特定于类型的优化,并确保有效实施SQL Server语义,例如可为 null 性、约束方面、按值和标准异常处理。 通过为确切类型的参数生成代码,可以避免跨调用边界的类型强制或包装对象创建开销(称为“装箱”)。

然后,生成的存根将编译为本机代码,并使用 CLR 的 JIT (实时) 编译服务针对执行SQL Server的特定硬件体系结构进行优化。 JIT 服务在方法级别调用,并允许SQL Server宿主环境创建跨SQL Server和 CLR 执行的单个编译单元。 编译存根之后,生成的函数指针即成为函数的运行时实现。 此代码生成方法确保不会发生与运行时反射或元数据访问相关的其他调用开销。

在 SQL Server 和 CLR 之间快速转换

编译过程生成的函数指针可以在运行时通过本机代码调用。 对于标量值用户定义函数,可基于每行调用此函数。 为了最大程度地降低SQL Server与 CLR 之间的转换成本,包含任何托管调用的语句都有一个启动步骤来标识目标应用程序域。 该标识步骤减少每行的转换成本。

性能注意事项

下面汇总了特定于 SQL Server 中的 CLR 集成的性能注意事项。 有关详细信息,请参阅 MSDN 网站上的“在 SQL Server 2005 中使用 CLR 集成”。 有关托管代码性能的一般信息,请参阅 MSDN 网站上的“提高 .NET 应用程序性能和可伸缩性”。

用户定义函数

与 Transact-SQL 用户定义函数相比,CLR 函数受益于更快的调用路径。 此外,托管代码在过程代码、计算和字符串操作方面比 Transact-SQL 具有决定性的性能优势。 需要大量计算且不执行数据访问的 CLR 函数采用托管代码编写的效果更好。 但是,与 CLR 集成相比,Transact-SQL 函数执行数据访问的效率更高。

用户定义聚合

托管代码的性能大大优于基于游标的聚合。 托管代码的执行速度通常略慢于内置SQL Server聚合函数。 如果存在本机内置聚合函数,建议您使用该函数。 对于所需聚合不受本机支持的情况,出于性能原因,请考虑使用 CLR 用户定义聚合,而不是基于游标的实现。

流式表值函数

应用程序通常需要返回一个表作为调用函数的结果。 示例包括从文件读取表格格式数据作为导入操作的一部分,并将逗号分隔值转换为关系表示形式。 通常,您可以通过在调用方使用结果表之前具体化和填充此结果表来实现此目的。 CLR 与 SQL Server 的集成引入了一种新的扩展性机制,称为流式处理表值函数 (STVF) 。 托管 STVF 的性能优于可比扩展存储过程实现的性能。

STVF 是返回 IEnumerable 接口的 托管函数。 IEnumerable 具有导航 STVF 返回的结果集的方法。 调用 STVF 时,返回的 IEnumerable 将直接连接到查询计划。 查询计划在需要提取行时调用 IEnumerable 方法。 使用此迭代模型,结果在第一行生成之后即可使用,而不需要等到整个表填充完。 还可以极大地减少调用该函数而占用的内存。

数组与游标

当 Transact-SQL 游标必须遍历更容易表示为数组的数据时,可以使用托管代码显著提高性能。

字符串数据

SQL Server字符数据(如 varchar)可以是托管函数中的 SqlString 或 SqlChars 类型。 SqlString 变量将整个值的实例创建到内存中。 SqlChars 变量提供可用于获得更好性能和可扩展性的流式接口,而无需将整个值的实例创建到内存中。 这对于大型对象 (LOB) 数据尤为重要。 此外,可以通过 SqlXml.CreateReader () 返回的流式处理接口访问服务器 XML 数据。

CLR 与扩展存储过程

允许托管过程向客户端回发结果集的 Microsoft.SqlServer.Server 应用程序编程接口 (API) 的性能优于扩展存储过程使用的开放式数据服务 (ODS) API。 此外,System.Data.SqlServer API 支持 SQL Server 2005 (9.x) 中引入的数据类型,如 xml、varchar (max ) 、nvarchar (max) 和 varbinary (max ) ,而 ODS API 尚未扩展以支持新的数据类型。

使用托管代码,SQL Server管理内存、线程和同步等资源的使用。 这是因为公开这些资源的托管 API 是在SQL Server资源管理器之上实现的。 相反,SQL Server无法查看或控制扩展存储过程的资源使用情况。 例如,如果扩展存储过程消耗过多的 CPU 或内存资源,则无法通过SQL Server来检测或控制这一点。 但是,使用托管代码,SQL Server可以检测到给定线程长时间未生成,然后强制任务生成,以便可以计划其他工作。 因此,使用托管代码可以提高可扩展性,并改善系统资源使用情况。

托管代码可能带来维护执行环境和执行安全检查所需的额外开销。 例如,在SQL Server内部运行并且需要从托管代码到本机代码的大量转换 (,因为SQL Server在移出到本机代码并返回) 时,需要对特定于线程的设置执行额外的维护。 因此,对于托管代码与本机代码之间频繁转换的情况,扩展存储过程的表现可能明显优于SQL Server内运行的托管代码。

注意

建议您不要开发新的扩展存储过程,因为已不推荐使用此功能。

用户定义类型的本机序列化

用户定义类型 (UDT) 是作为标量类型系统的扩展性机制设计的。 SQL Server为名为 Format.Native 的 UDT 实现序列化格式。 在编译期间,检查该类型的结构以便生成针对该特定类型定义自定义的 MSIL。

本机序列化是SQL Server的默认实现。 用户定义序列化调用由类型作者定义的方法来执行序列化。 应尽可能使用 Format.Native 序列化来获得最佳性能。

可比 UDT 的规范化

关系操作(例如对 UDT 进行排序和比较)是针对值的二进制表示形式直接执行的。 通过在磁盘上存储 UDT 状态的规范化(二进制排序)表示形式可以实现此目的。

规范化具有两个优点:避免构造类型实例和方法调用开销,进而极大地降低了比较操作的成本;为 UDT 创建二进制域,支持构造直方图、索引以及该类型的值的直方图。 因此,规范化 UDT 的性能配置文件类似于未涉及方法调用的操作的本机内置类型性能配置文件。

可扩展内存使用量

为了使托管垃圾回收在SQL Server中良好执行和缩放,请避免大型单一分配。 大小大于 88 千字节 (KB) 的分配将被放置到大对象堆,这将导致垃圾回收的性能和调整结果远不如多个较小分配的性能和调整结果。 例如,如果需要分配一个大型多维数组,最好分配一个交错(分散)数组。

另请参阅

CLR 用户定义类型