Professional SQL Server 2005 Administration (Wrox Professional Guides)

In this section, we discuss what it means to be a .NET runtime host, deploying .NET assemblies, maintaining security for .NET assemblies, and monitoring performance. Also included is a short example demonstrating how to debug a .NET assembly. This section is not about how to write a cool .NET assembly, although we do create a small assembly to further your understanding of how it works. This section focuses more on administration.

Note

For more information on SQL Server Integration Services, see Professional SQL Server 2005 Integration Services, by Brian Knight et al (Wrox, 2006); and to learn more about programming in SQL CLR, see Professional SQL Server 2005 CLR Stored Procedures, Functions, and Triggers, by Derek Comingore and Douglas Hinson (Wrox, 2007).

SQL Server as .NET Runtime Host

A runtime host is defined as any process that loads the .NET runtime and runs code in the managed environment. The database programming model in SQL Server 2005 is significantly enhanced by hosting the Microsoft .NET Framework 2.0 Common Language Runtime (CLR). With CLR integration, also called SQLCLR, SQL Server 2005 enables .NET programmers to write stored procedures, user-defined functions, and triggers in any .NET compatible language, particularly C# and VB.NET.

The .NET code that SQL Server runs is completely isolated from SQL Server itself. Of course .NET code runs in the SQL Server process space, but SQL Server uses a construct in .NET called the AppDomain (Application Domain) to completely isolate all resources that the .NET code uses from the resources that SQL Server uses. The AppDomain protects SQL Server from all malicious use of system resources. Keep in mind that SQL Server manages its own thread scheduling, synchronization and locking, and of course memory management. There are other .NET hosts, such as ASP.NET and Internet Explorer, for which these tasks are managed by the CLR. There is no conflict of interest between SQL Server and CLR regarding who manages the resources. Obviously SQL Server wins because reliability, security, and performance are of the utmost importance for SQL Server, and changes have been made in how the managed hosting APIs work as well as in how the CLR works internally.

Figure 8-17 shows how SQL Server hosts the CLR .NET 2.0 hosts want to have hooks into the CLR's resource management and allocations. They achieve that by calling ICLRRunTimeHost. These APIs calls a shim DLL, MSCOREE.DLL, whose job is to load the runtime. The host (SQL Server) then can call ICLRHostRunTime::SetHostControl(). This method points to the IHostControl interface, which contains the method GetHostControl, which the CLR can call to delegate tasks such as thread management and memory management to the host (SQL Server). SQL Server uses this interface to take control of some functions that the CLR calls down to the OS directly.

image from book Figure 8-17

The CLR calls SQL Server APIs for creating threads, both for running user code and for its own internal use. SQL Server uses a cooperative thread schedule model, whereas managed code uses preemptive thread scheduling. In cooperative thread scheduling, the thread must voluntarily yield control of the processor, while in preemptive thread scheduling the processor takes control back from the thread after its time slice has expired. Some greedy managed code may not yield for a long time and may monopolize the CPU time. SQL Server can identify those "runaway" threads, suspend them, and put them back in the queue. Some threads that are identified repeatedly as runaway threads are not allowed to run for a given period of time, which enables other worker threads to run.

Only one instance of the runtime engine can be loaded into the process space during the lifetime of a process. It is not possible to run multiple versions of the CLR within the same host.

Application Domains

In .NET, processes can be subdivided into execution zones called application domains (AppDomains) within a host (SQL Server) process. The managed code assemblies can be loaded and executed in these AppDomains. Figure 8-18 shows the relationship between a process and AppDomains.

Figure 8-18

SQL Server isolates code between databases by using AppDomains. This means that for each database, if you have registered an assembly and you invoke some function from that assembly, an AppDomain is created for that database. Only one AppDomain is created per database. Later you will look at some DMVs that you can use to find out information about AppDomains.

T-SQL versus CLR

We mentioned earlier that you can write stored procedures, triggers, and functions using CLR-compatible languages. Does that mean DBAs need to take a crash course on C# or VB.NET? No, but you need to at least understand why code is returned in .NET languages versus T-SQL. You are the one who is going to run the production environments, so you need to be part of the decision regarding which set of tools is chosen for your application.

The data manipulation can be broadly categorized into two parts: a declarative query language and a procedural query language. The declarative query language is composed of SELECT,INSERT,UPDATE, and DELETE statements, while a procedural language is composed of triggers, cursors, and WHILE statements. SQL CLR integration provides an alternative support to the procedural portion of T-SQL. Database applications should look to procedural programming if you cannot express the business logic you need with a query language. T-SQL is best when you perform set-based operations. It can take advantage of the query processor, which is best able to optimize the set operations. Do not write CLR code to start processing row-by-row operations, which you can do best with T-SQL in set operations. However, if your application requires performing complex calculations on a per-row basis over values stored in database tables, you can access the results from your table by first using SELECT and then by performing row-by-row operations using CLR code. Of course, there is a transition cost between the CLR and SQL layer, but if you are performing operations on high-volume data, the transition cost may be negligible.

Extended Stored Procedure versus CLR

To write server-side code with logic that was difficult to write in T-SQL, the only option prior to SQL Server 2005 was to write extended stored procedures (XPs). CLR integration in SQL Server 2005 now provides a more robust way to do those operations with managed code. The following list describes some of the benefits SQL CLR integration provides over extended stored procedures:

These advantages do not apply if you register the assemblies with the UNSAFE permission set. Most extended stored procedures can be replaced, especially considering that Managed C++ is available as a coding option.

Enabling CLR Integration

By default, CLR integration is disabled. You cannot execute any .NET code until you intentionally change the configuration in SQL Server to allow CLR integration. You can do that with the GUI, the Surface Area Configuration tool, or using a T-SQL script. The shortcut to the tool is located in StartAll ProgramsSQL Server 2005Configuration Tools. Select the SQL Server Surface Area Configuration option, and then select Surface Area Configuration for Features. Figure 8-19 shows where you can enable or disable CLR integration within the Surface Area Configuration tool.

Figure 8-19

If you prefer to use a T-SQL script, the following script will do the same thing:

EXEC sp_configure 'clr enabled', 1 GO RECONFIGURE GO SELECT * FROM sys.configurations WHERE name = 'clr enabled'

You can check serverwide configuration using the catalog view sys.configuration. Look in the value_in_use column for the current configured value in the sys.configuration view.

Creating the CLR Assembly

In this section we will create a small C# Table Valued Function in order to demonstrate how to deploy an assembly, maintain security, and look for CLR objects in the database. The following code will access the specified directory and list all the files and attributes as a tabular result set. This example also illustrates how impersonation works. If you don't have Visual Studio 2005 installed, you can just take the compiled DLL from this book's Web site at www.wrox.com, and register that in the database, as shown later, or you can use the command we provide to compile the DLL from the .cs file using csc.exe (the C# compiler), which ships free with the .NET SDK. The following code will create a Table Valued Function:

using System; using System.Collections; using System.Collections.Generic; using System.Text; using System.IO; using Microsoft.SqlServer.Server; using System.Data.SqlTypes; using System.Security.Principal; public class FileDetails { [SqlFunction(DataAccess = DataAccessKind.Read, FillRowMethodName = "FillFileRow" ,TableDefinition = "name nvarchar(4000), creationTime datetime, lastAccessTime datetime, lastWriteTime datetime, isDirectory bit, isReadOnly bit, length bigint" )] public static IEnumerable GetFileDetails(string directoryPath) { try { DirectoryInfo di = new DirectoryInfo(directoryPath); return di.GetFiles(); } catch (DirectoryNotFoundException dnf) { return new string[1] { dnf.ToString() }; } catch (UnauthorizedAccessException ave) { return new string[1] { ave.ToString() }; } } [SqlFunction(DataAccess = DataAccessKind.Read, FillRowMethodName = "FillFileRowWithImpersonation", TableDefinition = "name nvarchar(4000), creationTime datetime, lastAccessTime datetime, lastWriteTime datetime, isDirectory bit, isReadOnly bit, length bigint")] public static IEnumerable GetFileDetailsWithImpersonation(string directoryPath) { WindowsIdentity clientId = null; WindowsImpersonationContext impersonatedUser = null; clientId = SqlContext.WindowsIdentity; try { try { impersonatedUser = clientId.Impersonate(); if (impersonatedUser != null) return GetFileDetails(directoryPath); else return null; } finally { if (impersonatedUser != null) impersonatedUser.Undo(); } } catch { throw; } } public static void FillFileRow(object fileData, out SqlString name, out SqlDateTime creationTime, out SqlDateTime lastAccessTime, out SqlDateTime lastWriteTime, out SqlBoolean isDirectory, out SqlBoolean isReadOnly, out SqlInt64 length) { FileInfo info = fileData as FileInfo; if (info == null) { name = "Error, directory list failed: "+ fileData.ToString(); creationTime = SqlDateTime.Null; lastAccessTime = SqlDateTime.Null; lastWriteTime = SqlDateTime.Null; isDirectory = SqlBoolean.Null; isReadOnly = SqlBoolean.Null; length = SqlInt64.Null; } else { name = info.Name; creationTime = info.CreationTime; lastAccessTime = info.LastAccessTime; lastWriteTime = info.LastWriteTime; isDirectory = (info.Attributes & FileAttributes.Directory) > 0; isReadOnly = info.IsReadOnly; length = info.Length; } } public static void FillFileRowWithImpersonation(object fileData, out SqlString name, out SqlDateTime creationTime, out SqlDateTime lastAccessTime, out SqlDateTime lastWriteTime, out SqlBoolean isDirectory, out SqlBoolean isReadOnly, out SqlInt64 length) { WindowsIdentity clientId = null; WindowsImpersonationContext impersonatedUser = null; clientId = SqlContext.WindowsIdentity; try { try { impersonatedUser = clientId.Impersonate(); if (impersonatedUser != null) FillFileRow(fileData, out name, out creationTime, out lastAccessTime, out lastWriteTime, out isDirectory, out isReadOnly, out length); else { FillFileRow("Error: Impersonation failed!", out name, out creationTime, out lastAccessTime, out lastWriteTime, out isDirectory, out isReadOnly, out length); } } finally { if (impersonatedUser != null) impersonatedUser.Undo(); } } catch { throw; } } }

The preceding code has two main methods: GetFileDetails and GetFileDetailsWithImpersonation. The method GetFileDetails calls the method FillFileRow. The method GetFileDetailsWith Impersonation calls the methods FillFileRowWithImpersonation, GetFileDetails, FillFileRowWithImpersonation, and FillFileRow, in that order.

Later we will define two SQL TVFs called GetFileDetails and GetFileDetailsWithImpersonation. The function GetFileDetails will call the method GetFileDetails from this code. This method just gets the files list and other details such as creation time and last modified time using the SQL Server service account. The FillFileRow method will get this property and return it to the SQL TVF. The SQL TVF function GetFileDetailsWithImpersonation will call the method GetFileDetailsWith Impersonation, which sets the impersonation to your login; and when it calls the GetFileDetails method, it uses your login identity rather than the SQL Server service account. You'll see how to call these SQL TVF functions later.

Now you need to compile the code and create an assembly called GetFileDetails.dll. If you have Visual Studio 2005, you can open the solution GetFileDetails.sln. Build the project by clicking BuildBuild GetFileDetails. That will create the GetFileDetails.dll file in the debug directory under the bin directory in your solution directory. You can deploy this assembly using Visual Studio 2005, but we will do that manually so that you know how to write T-SQL script to deploy the assembly in a database.

If you do not have Visual Studio 2005, you can use following command-line utility, csc.exe, to build GetFileDetails.dll:

C:\>C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Csc.exe /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Data.dll /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll /reference:C :\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll /keyfile:c:\assembly \keypair.snk /out:c:\assembly\GetFileDetails.dll /target:library C:\assembly\Get FileDetails.cs

The /out parameter specifies the target location where GetFileDetails.dll will be created. The /target parameter specifies that this is a library (DLL). When you specify GetFileDetails.cs, you may have to specify the full path of the file if you are not in the directory where GetFileDetails.cs is located. In addition, the option /keyfile is used to sign the assembly. You will see why we have used this option when we register the assembly.

Now that you have created the DLL, you need to deploy the assembly and associate it with SQL user-defined functions.

Deploying the Assembly

Managed code is compiled and then deployed in units called assemblies. An assembly is packaged as a DLL or executable (.exe). The executable can run on its own, but a DLL needs to be hosted in an existing application. We have created a DLL in this case because SQL Server will host it. You now have to register this assembly using the new DDL statement CREATE ASSEMBLY, like this:

CREATE ASSEMBLY assembly_name [ AUTHORIZATION owner_name ] FROM { <client_assembly_specifier> | <assembly_bits> [ ,...n ] } [ WITH PERMISSION_SET = { SAFE | EXTERNAL_ACCESS | UNSAFE } ] [ ; ] <client_assembly_specifier> :: = '[\\computer_name\]share_name\[path\]manifest_file_name' | '[local_path\]manifest_file_name' <assembly_bits> :: = { varbinary_literal | varbinary_expression }

The arguments of the CREATE ASSEMBLY statement are as follows:

Security Notes

You have just learned about what code access security can be applied to an assembly, but this section provides a bit more detail about the various options.

SAFE is the most restrictive option and the default. If the assembly doesn't need access to external resources, this is the permission you should use when you register the assembly.

You should use EXTERNAL_ACCESS whenever you need access to external resources. Keep in mind that when an assembly with this permission accesses external resources, it uses the SQL Server service account to do so. So make sure during the code review that impersonation is used while accessing the external resource. If impersonation is used, the external resources would be accessed under the caller's security context. Because this code can access external resources, before you register this assembly, you have to either set the TRUSTWORTHY bit in the database to 1, or sign the assembly, as you'll see later. The EXTERNAL_ACCESS assembly includes the reliability and scalability of the SAFE assembly.

We do not recommend using an UNSAFE assembly, because it could compromise SQL Server. It is no better than an extended stored procedure. You should have a very solid reason to use this option and should carefully examine what the assembly does, documenting it exactly. Keep in mind that when an assembly with this permission accesses external resources, it uses the SQL Server service account to do so.

The following table should help clarify how SQL Server applies rules for accessing resources outside of SQL Server when the assembly is created with either with the EXTERNAL_ACCESS or UNSAFE permission sets.

Open table as spreadsheet

IF

THEN

If the execution context is SQL Server login …

attempts to access external resources are denied and a security exception is raised.

If the execution context corresponds to the Windows login and the execution context is the original caller …

external resources are accessed under the SQL Server service account.

If the execution context corresponds to the Windows login and the execution context is not the original caller …

attempts to access external resources are denied and a security exception is raised.

Registering the Assembly

Now you are ready to register the assembly into a database. Open the solution Sample3.

Open and run the script CreateDB.sql. This will create a database called CLRDB. Now open the script CreateAssembly.sql, shown here:

--------------------- USE CLRDB GO IF OBJECT_ID('GetFileDetails') IS NOT NULL DROP FUNCTION [GetFileDetails]; GO IF OBJECT_ID('GetFileDetailsWithImpersonation') IS NOT NULL DROP FUNCTION [GetFileDetailsWithImpersonation]; GO IF EXISTS (SELECT * FROM sys.assemblies WHERE [name] = 'GetFileDetails') DROP ASSEMBLY [GetFileDetails]; GO --------------------- USE master GO IF EXISTS (SELECT * FROM sys.server_principals WHERE [name] = 'ExternalAccess_Login') DROP LOGIN ExternalAccess_Login; GO IF EXISTS (SELECT * FROM sys.asymmetric_keys WHERE [name] = 'ExternalAccess_Key') DROP ASYMMETRIC KEY ExternalAccess_Key; GO CREATE ASYMMETRIC KEY ExternalAccess_Key FROM EXECUTABLE FILE = 'C:\Assembly\GetFileDetails.dll' GO CREATE LOGIN ExternalAccess_Login FROM ASYMMETRIC KEY ExternalAccess_Key GO GRANT EXTERNAL ACCESS ASSEMBLY TO ExternalAccess_Login GO --------------------- USE CLRDB GO CREATE ASSEMBLY GetFileDetails FROM 'C:\Assembly\GetFileDetails.dll' WITH PERMISSION_SET = EXTERNAL_ACCESS; GO

Before you register the assembly with SQL Server, you must arrange for the appropriate permissions. Assemblies with UNSAFE or EXTERNAL_ACCESS permissions can only be registered and operate correctly if either the database TRUSTWORTHY bit is set (ALTER DATABASE CLRDB SET TRUSTWORTHY ON), or the assembly is signed with a key, that key is registered with SQL Server, a server principal is created from that key, and that principal is granted the external access or unsafe assembly permission. Here you'll use the latter approach so that you can also see how you can sign the assembly and register it, because the first approach is a simple matter of just setting the TRUSTWORTHY bit to ON. The latter approach is also more granular, and therefore safer. You should never register an assembly with SQL Server (especially with EXTERNAL_ACCESS or UNSAFE permissions) without thoroughly reviewing the source code of the assembly to make sure that its actions do not pose an operational or security risk.

In the previous code, you created a login called ExternalAccess_Login from the asymmetric key ExternalAccess_Key. When you created the assembly using the command-line option csc.exe, you specified the /keyfile option. Because of that option, the assembly GetFileDetails.dll was signed. You are now registering that key into SQL Server because in the CREATE ASYMMETRIC KEY statement in this code you specified the EXECUTABLE FILE option to point to the signed DLL. You have granted the EXTERNAL_ACCESS permission to ExternalAccess_Login. Then you create the assembly with the CREATE ASSEMBLY command. The assembly is now registered in the CLRDB database. Remember that once the assembly is registered in the database, you don't need the assembly file .dll, so you can move your database from server to server without worrying about that file. Of course, you have to put the source code from which you have created the DLL into your source control so that in the future, if you need to modify anything, you will do that in the source. If you want to set the TRUSTWORTHY bit to 1 for the database, you do not need to perform any of these steps except for creating the assembly.

Now open the CreateTVF.sql file and run it. The code is shown here:

USE CLRDB GO IF OBJECT_ID('GetFileDetails') IS NOT NULL DROP FUNCTION GetFileDetails GO CREATE FUNCTION GetFileDetails(@directory nvarchar(256)) RETURNS TABLE ( Name nvarchar(max) ,CreationTime datetime ,LastAccessTime datetime ,LastWriteTime datetime ,IsDirectory bit ,IsReadOnly bit ,Length bigint ) AS EXTERNAL NAME [GetFileDetails].[FileDetails].[GetFileDetails] GO IF OBJECT_ID('GetFileDetailsWithImpersonation') IS NOT NULL DROP FUNCTION GetFileDetailsWithImpersonation GO CREATE FUNCTION GetFileDetailsWithImpersonation(@directory nvarchar(256)) RETURNS TABLE ( Name nvarchar(max) ,CreationTime datetime ,LastAccessTime datetime ,LastWriteTime datetime ,IsDirectory bit ,IsReadOnly bit ,Length bigint ) AS EXTERNAL NAME [GetFileDetails].[FileDetails].[GetFileDetailsWithImpersonation] GO

You are creating two Table Value Functions, GetFileDetails and GetFileDetailsWithImpersonation. The T-SQL function GetFileDetails calls the method GetFileDetails of the class FileDetails from the assembly GetFileDetails. The T-SQL function GetFileDetailsWithImpersonation calls the method GetFileDetailsWithImpersonation of the class FileDetails from the assembly GetFile Details. Now you have mapped the T-SQL TVF to the methods in the assembly GetFileDetails.dll.

Now it's time to test these functions. Open the file TestGetFileDetails.sql. The code is shown here:

USE CLRDB GO DECLARE @TestDir nvarchar(256); SELECT @TestDir = 'D:\test' SELECT [Name], CreationTime, LastAccessTime, LastWriteTime, IsDirectory, IsReadOnly, Length FROM GetFileDetails(@TestDir) SELECT [Name], CreationTime, LastAccessTime, LastWriteTime, IsDirectory, IsReadOnly, Length FROM GetFileDetailsWithImpersonation(@TestDir)

In the preceding script, you set the variable @TestDir to 'D:\test' because you want to list all the files in the directory D:\test. You should set the permission on the directory D:\test so that only you have permission to that directory. If you have set the permission correctly, you will see the results shown in Figure 8-20 when you run the script.

Figure 8-20

The first function, GetFileDetails, fails with an Unauthorized Access exception. Because this assembly is registered with the EXTERNAL_ACCESS permission set, SQL Server uses the SQL Server service account to access the external resource, and because you set the permission on the directory D:\test so that only you can access it, the function failed. Of course, in this case the SQL Server Service account is not running under the security context with which you have run the function.

The second function, GetFileDetailsWithImpersonation, succeeded because you set the impersonation to the user who is connecting to SQL Server to execute the function. Now SQL Server will access the directory D:\test under the security context of the user executing the function, rather than the SQL Server service account.

ALTER ASSEMBLY

You can change the properties of an assembly or change the assembly code using the ALTER ASSEMBLY statement. Reload the assembly with the modified code and it will refresh the assembly to the latest copy of the .NET Framework module that holds its implementation, adding or removing files associated with it:

ALTER ASSEMBLY GetFileDetails FROM '\\MyMachine\Assembly\GetFileDetails.dll'

If you decide that an assembly should only be called by another assembly and not from outside, you can change the visibility of the assembly as follows:

ALTER ASSEMBLY GetFileDetails SET VISIBILTY = OFF

For full syntax details, you can refer to Books Online.

DROP ASSEMBLY

You can drop an assembly using the DROP ASSEMBLY statement. If the assembly is referenced by other objects, such as user-defined functions or stored procedures, you cannot drop the assembly until you drop those dependent objects:

DROP ASSEMBLY GetFileDetails

There is a NO DEPENDENTS option to the DROP ASSEMBLY statement. If you don't specify this option, all the dependent assemblies will also be dropped:

DROP ASSEMBLY GetFileDetails WITH NO DEPENDENTS

Cataloging Objects

You can get information about different CLR objects, such as CLR stored procedures and CLR functions, using the queries described in the following sections.

Assemblies

The following are some queries from catalog views you can use to get more information on registered assemblies.

The sys.assemblies view gives you all the registered assemblies in the database:

SELECT * FROM sys.assemblies

The following view will give you all the files associated with the assembly:

SELECT a.Name AS AssemblyName, f.name AS AssemblyFileName FROM sys.assembly_files f JOIN sys.assemblies a ON a.assembly_id = f.assembly_id

The following view will provide information about the assembly, its associated class, the methods in the class, and the SQL object associated with each assembly:

SELECT a.Name AS AssemblyName ,m.Assembly_Class ,m.Assembly_Method ,OBJECT_NAME(um.object_id) AS SQL_Object_Associated ,so.type_desc AS SQL_Object_Type ,u.name AS Execute_As_Principal_Name FROM sys.assembly_modules m JOIN sys.assemblies a ON a.assembly_id = m.assembly_id LEFT JOIN sys.module_assembly_usages um ON um.assembly_id = a.assembly_id LEFT JOIN sys.all_objects so ON so.object_id = um.object_id LEFT JOIN sys.sysusers u ON u.uid = m.Execute_As_Principal_id

CLR Stored Procedures

This query will give you the CLR stored procedure and other details associated with it:

SELECT schema_name(sp.schema_id) + '.' + sp.[name] AS [SPName] ,sp.create_date ,sp.modify_date ,sa.permission_set_desc AS [Access] FROM sys.procedures AS sp JOIN sys.module_assembly_usages AS sau ON sp.object_id = sau.object_id JOIN sys.assemblies AS sa ON sau.assembly_id = sa.assembly_id WHERE sp.type = 'PC'

CLR Trigger Metadata

This query will give you the CLR trigger's details:

SELECT schema_name(so.schema_id) + '.' + tr.[name] AS [TriggerName] ,schema_name(so.schema_id) + '.' + object_name(tr.parent_id) AS [Parent] ,a.name AS AssemblyName ,te.type_desc AS [Trigger Type] ,te.is_first ,te.is_last ,tr.create_date ,tr.modify_date ,a.permission_set_desc AS Aseembly_Permission ,tr.is_disabled ,tr.is_instead_of_trigger FROM sys.triggers AS tr JOIN sys.objects AS so ON tr.object_id = so.object_id JOIN sys.trigger_events AS te ON tr.object_id = te.object_id JOIN sys.module_assembly_usages AS mau ON tr.object_id = mau.object_id JOIN sys.assemblies AS a ON mau.assembly_id = a.assembly_id WHERE tr.type_desc = N'CLR_TRIGGER'

CLR Scalar Function

The following query will give you the CLR scalar function:

SELECT schema_name(so.schema_id) + '.' + so.[name] AS [FunctionName] ,a.name AS AssemblyName ,so.create_date ,so.modify_date ,a.permission_set_desc AS Aseembly_Permission FROM sys.objects AS so JOIN sys.module_assembly_usages AS sau ON so.object_id = sau.object_id JOIN sys.assemblies AS a ON sau.assembly_id = a.assembly_id WHERE so.type_desc = N'CLR_SCALAR_FUNCTION'

CLR Table Valued Function

This query will give you all CLR Table Valued Functions:

SELECT schema_name(so.schema_id) + N'.' + so.[name] AS [FunctionName] ,a.name AS AssemblyName ,so.create_date, so.modify_date ,a.permission_set_desc AS Aseembly_Permission FROM sys.objects AS so JOIN sys.module_assembly_usages AS sau ON so.object_id = sau.object_id JOIN sys.assemblies AS a ON sau.assembly_id = a.assembly_id WHERE so.type_desc = N'CLR_TABLE_VALUED_FUNCTION'

CLR User-Defined Aggregates

The following query will give you the CLR user-defined aggregates:

SELECT schema_name(so.schema_id) + N'.' + so.[name] AS [FunctionName] ,a.name AS AssemblyName ,so.create_date ,so.modify_date ,a.permission_set_desc AS Aseembly_Permission FROM sys.objects AS so JOIN sys.module_assembly_usages AS mau ON so.object_id = mau.object_id JOIN sys.assemblies AS a ON mau.assembly_id = a.assembly_id WHERE so.type_desc = N'AGGREGATE_FUNCTION'

CLR User Defined Types

This query will provide a list of the CLR user-defined types:

SELECT st.[name] AS [TypeName] ,a.name AS [AssemblyName] ,a.permission_set_desc AS AssemblyName ,a.create_date AssemblyCreateDate ,st.max_length ,st.[precision] ,st.scale ,st.collation_name ,st.is_nullable FROM sys.types AS st JOIN sys.type_assembly_usages AS tau ON st.user_type_id = tau.user_type_id JOIN sys.assemblies AS a ON tau.assembly_id = a.assembly_id

Application Domains

We talked earlier about application domains. Here we look at some DMVs that will give you more information about AppDomains in your servers. The following DMV will provide information about all the AppDomains currently loaded and their state:

SELECT * FROM sys.dm_clr_appdomains

Recall that an AppDomain is created for each database. An AppDomain has different states described in the column state in the DMV sys.dm_clr_appdomains. The following list describes the different possible AppDomain states:

This view is helpful when the AppDomain in question is loaded, but it doesn't help much when the AppDomain is unloaded. The following query will give you all the states an AppDomain has gone through so you can see what exactly happened to it:

SELECT Timestamp ,rec.value('/Record[1]/AppDomain[1]/@address', 'nvarchar(10)') as Address ,rec.value('/Record[1]/AppDomain[1]/@dbId', 'int') as DBID ,d.name AS DatabaseName ,rec.value('/Record[1]/AppDomain[1]/@ownerId', 'int') as OwnerID ,u.Name AS AppDomainOwner ,rec.value('/Record[1]/AppDomain[1]/@type', 'nvarchar(32)') as AppDomainType ,rec.value('/Record[1]/AppDomain[1]/State[1]', 'nvarchar(32)')as AppDomainState FROM ( SELECT timestamp, cast(record as xml) as rec FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = 'RING_BUFFER_CLRAPPDOMAIN' ) T JOIN sys.sysdatabases d ON d.dbid = rec.value('/Record[1]/AppDomain[1]/@dbId', 'int') JOIN sys.sysusers u on u.uid = rec.value('/Record[1]/AppDomain[1]/@ownerId', 'int') ORDER BY timestamp DESC

Note that the DMV used in the sys.dm_os_ring_buffers query is not documented, so you should not rely on it for future releases of SQL Server. The DatabaseName column will indicate which database the AppDomain belongs to and what stages it has gone through.

Performance Monitoring

You can use Windows System Monitor, DMVs, and SQL Profiler for SQLCLR performance monitoring.

System Monitor

You can use Windows System Monitor (PerfMon.exe) to monitor CLR activities for SQL Server. Use the counter in the .NET CLR group in System Monitor. Choose the sqlservr instance when you monitor CLR counters for SQL Server. Use the following counters to understand the health and activity of the programs running under the SQL-hosted environment:

SQL Profiler

SQL Profiler is not much use for monitoring the CLR because it has only one event for the CLR, called Assembly Load. This event tells you when an assembly is loaded with the message "Assembly Load Succeeded." If the load fails, it will provide a message indicating which assembly load failed and some error code.

DMVs

The DMVs related to SQL CLR provide very useful information on CLR memory usage, which assemblies are currently loaded, the current requests waiting in CLR, and more. The following sections describe some queries that help in monitoring the CLR.

SQL CLR Memory Usage

You can find out how much memory SQL CLR is using in KB with the following query:

SELECT single_pages_kb + multi_pages_kb + virtual_memory_committed_kb FROM sys.dm_os_memory_clerks WHERE type = 'MEMORYCLERK_SQLCLR'

The first column, single_pages_kb, is the memory allocated in the SQL buffer pool. The second column, multi_pages_kb, indicates the memory allocated by the SQL CLR host outside of the buffer pool. The third column, virtual_memory_committed_kb, indicates the amount of memory allocated by the CLR directly through bulk allocation (instead of heap allocation) through SQL server.

Note that you will see a second row with 0 if you don't have a NUMA (non-uniform memory access) system. In a NUMA system, each node has its own memory clerk, so in that case, you would have to add the node totals to get the total memory usage.

Loaded Assemblies

The following query will give you all the currently loaded assemblies:.

SELECT a.name AS Assembly_Name ,ad.Appdomain_Name ,clr.Load_Time FROM sys.dm_clr_loaded_assemblies AS clr JOIN sys.assemblies AS a ON clr.assembly_id = a.assembly_id JOIN sys.dm_clr_appdomains AS ad ON clr.appdomain_address = ad.appdomain_address

CLR Request Status

The following query will give you current request status in the SQL CLR:

SELECT session_id, request_id, start_time, status, command, database_id, wait_type, wait_time, last_wait_type, wait_resource, cpu_time, total_elapsed_time, nest_level, executing_managed_code FROM sys.dm_exec_requests WHERE executing_managed_code = 1

Time Spent in SQL CLR By a Query

The next query provides interesting statistics on a CLR query, such as execution count (how many times that query was executed), logical reads, physical reads, time elapsed, and last execution time:

SELECT (SELECT text FROM sys.dm_exec_sql_text(qs.sql_handle)) AS query_text, qs.* FROM sys.dm_exec_query_stats AS qs WHERE qs.total_clr_time > 0

Finding the .NET Framework Version in SQL Server

If the .NET Framework is loaded you can use the following query to determine which .NET Framework version is used in SQL Server. (The .NET load is a lazy load in SQL Server. It is only loaded when any CLR assembly is called for the first time.)

SELECT value AS [.NET FrameWork Version] FROM sys.dm_clr_properties WHERE name = 'version'

If the .NET Framework is not loaded and you want to find out which .NET Framework version SQL Server is using, run the following query:

SELECT Product_Version AS [.NET FrameWork Version] FROM sys.dm_os_loaded_modules WHERE name LIKE N'%\MSCOREE.DLL'

Категории