我老是擔心使用CLR會造成內存方面的問題,比如內存不能回收。其實想一想也許我過濾了,畢竟.NET Framework能夠自己回收托管內存。既然在應用服務器上能使用,數據庫上似乎也可以使用。
希望聽一聽大家的經驗和見解~~
Peak Wong:
一般不用, 主要是DBA管理這種東西的時候會很麻煩, 因為自己寫的CLR很難保證沒有BUG, 一旦出BUG, 如果只是影響調用那一部分還好, 如果導致整個服務掛掉, 則很危險了(而且往往導致服務器掛掉還不一定查得出來是否CLR的問題)
另外, 部署和遷移也是要考慮的問題之一。還有感覺 CLR 還沒有想像中的那麼穩定(或者是與 SQL SERVER 的配合沒有想像中的好)
例如在處理 復制出錯的時候, 有時會調用一些存儲過程去查看出錯相關的信息, 這些存儲過程中的一部分就是 CLR 存儲過程, 經常會發現怎麼也調不成功, 說什麼內存錯誤什麼的, 但實際上空閒內存肯定是夠的.
而且一般第一次調用CLR的時候, 加載程序集花費的時間也不短, 所以如果 CLR 用得多, 內存又不太夠, 導致一些CLR程序集老是反復加載的話, 性能會非常低。
內存回收機制確實很智能,只不過只需要把比如打開文件啊,開數據庫啊什麼的關閉就可以了。就不會造成多余的系統開銷。如果害怕的話,可以寫段折構函數!以前學習的時候寫過一個例子,但是用處不打,可以借鑒一下。
C# code
using System;
using System.IO;
using System.Windows.Forms;
namespace nameSpace1
{
public class gcDemo:IDisposable
{
static int ctor_cnt = 0;
static int dtor_cnt = 0;
static public gcDemo c1;
static public StreamWriter ofile;
static int iterations;
private bool isDispose = false;
public void Dispose()
{
Dispose(true);
System.GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if(!isDispose)
{
if(disposing)
ofile.Close();
}
isDispose = true;
}
static public int Iterations
{
get
{
return iterations;
}
set
{
iterations = value;
}
}
static gcDemo()
{
string startupPath;
startupPath = Application.StartupPath;
ofile = new StreamWriter(new FileStream(startupPath+@"fx.txt",FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.None));
ofile.WriteLine("all trace output at {0}", startupPath+@"fx.txt");
Console.WriteLine("startupPath is :{0}", startupPath);
}
public gcDemo()
{
ofile.WriteLine("gcDemo Class Constructor: {0}", ++ctor_cnt);
ofile.Flush();
}
~gcDemo()
{
try
{
ofile.WriteLine("gcDemo Class Destructor: {0}", ++dtor_cnt);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
Dispose(false);
}
static public void gcObject()
{
for(int ix = 0;ix<gcDemo.Iterations;ix++)
c1 = new gcDemo();
}
}
class EntryPoint
{
public static void testM()
{
gcDemo.Iterations = 100;
gcDemo.gcObject();
}
static void Main()
{
testM();
gcDemo.c1.Dispose();
try
{
gcDemo.ofile.WriteLine("test");
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
System.Threading.Thread.Sleep(3000);
}
}
}
在上面的例子中看出,折構函數~gcDemo的特性為,系統能夠確保非托管代碼在程序執行後能夠得到回收。因為原理是~gcDemo的代碼被封裝在Finalize()方法的一個try塊內。並且把該方法調用放在finally中,由此確保了它的執行,但是缺點是,是在程序執行完畢後進行刪除對象需要處理兩次才行,第一次不處理,第二次刪除,由此可能對系統效率產生很大的影響。
比如我在~gcDemo程序中加入的
try
{
ofile.WriteLine("gcDemo Class Destructor: {0}", ++dtor_cnt);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
每次都要等把gcDemo Class Destructor:寫了以後才會收回資源,由此產生了很大的效率浪費。所以系統提供了另一種方法,就是IDisposable接口,使用Dispose()來把非托管代碼進行清空。
比如
gcDemo.c1.Dispose();
在執行完畢以後,就對ofile這個StreamWriter對象進行了關閉,那麼折構函數中的ofile.WriteLine方法就全部失效了!但是有個問題就是,需要程序人員手動進行設置,由此可能出現沒有設置到,或者因為程序出錯而出現的非托管垃圾駐留。因此微軟提供了上面的雙重方法來提供保險,對於程序員來說可以利用,但是也要根據情況來定了。
還有,就是如果要安全起見的話,那麼也可以采用IDisposable接口,然後用using來調用有非托管代碼的語句。如
using System;
using System.IO;
using System.Windows.Forms;
namespace nameSpace1
{
class myTest:IDisposable
{
static public StreamWriter ofile;
private string stPath;
public myTest()
{
stPath = Application.StartupPath;
ofile = new StreamWriter(new FileStream(stPath+@"fx2.txt", FileMode.OpenOrCreate));
ofile.WriteLine("This is a test file!");
}
#region IDisposable 成員
public void Dispose()
{
// TODO: 添加 myTest.Dispose 實現
ofile.Close();
}
#endregion
}
class EntryPoint
{
static void Main()
{
using(myTest my = new myTest())
{
myTest.ofile.WriteLine("這是我的第二次的寫入!");
}
try
{
myTest.ofile.WriteLine("這是我的第三次寫入!");
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
在該程序中,This is a test file!和這是我的第二次的寫入都可以被寫入到文件,但是由於using語句包裹的語句具有限制范圍,必須在{}和裡面有效果,因此出了using以後就自動調用了Dispose()了。由此,這是我的第三次寫入就發生了錯誤。但是要注意的是,對象一定要有Dispose()方法,要不不會被資源管理器給收回了的,比如,我把//ofile.Close()屏蔽了後,添加ofile.Flush()方法,那麼就會把三條語句一並寫入。
我們可以看一下我剛剛作的一個實例:
SQL code
-- 測試指定的目標字符串是否與給定的正則表達式模式匹
/*
IF ForString.IsMatchPattern(NULL, NULL)
= 1 PRINT 'yes' ELSE PRINT 'no'
DECLARE @target nvarchar(128)
SET @target = N'a阿阿abcdef
ppps'
IF ForString.IsMatchPattern(@target, '[u00ff-uffff]+[a-z]+s*[p]+')
= 1 PRINT 'yes' ELSE PRINT 'no'
IF ForString.IsMatchPattern(@target, '[u00ff-uffff]+[a-z]+s*[p]+$')
= 1 PRINT 'yes' ELSE PRINT 'no'
*/
IF OBJECT_ID(N'ForString.IsMatchPattern') IS NOT NULL DROP FUNCTION ForString.IsMatchPattern
GO -- 注意!記得復制此兩行 SQL 語句到此腳本文件開始處,以便可以刪除程序集
CREATE FUNCTION ForString.IsMatchPattern -- 測試指定的目標字符串是否與給定的正則表達式模式匹配
(
@target nvarchar(512) , -- 要測試的目標字符串
@pattern nvarchar(512) , -- 給定的正則表達式模式
@nullreturn bit = 0 -- 當 target 為 null 時的返回值
)
RETURNS bit -- 如果 target 或 pattern 為 null 則返回 false,否則返回測試的結果
AS EXTERNAL NAME Assemblies.[Assemblies.Functions.ScalarValued.ForString].IsMatchPattern
GO
-----------------------------------------------------------------------------------------------------
-- 對左操作數和右操作數進行二進制按位“或”運算
/*
PRINT ISNULL(ForBinary.OperationOr(NULL, NULL), 0x0)
PRINT ISNULL(ForBinary.OperationOr(0x123, NULL), 0x0)
PRINT ISNULL(ForBinary.OperationOr(0x60, 0x06), 0x0)
*/
IF OBJECT_ID(N'ForBinary.OperationOr') IS NOT NULL DROP FUNCTION ForBinary.OperationOr
GO -- 注意!記得復制此兩行 SQL 語句到此腳本文件開始處,以便可以刪除程序集
CREATE FUNCTION ForBinary.OperationOr -- 對左操作數和右操作數進行二進制按位“或”運算
(
@left varbinary(max) , -- 左操作數
@right varbinary(max) -- 右操作數
)
RETURNS varbinary(max)
AS EXTERNAL NAME Assemblies.[Assemblies.Functions.ScalarValued.ForBinary].OperationOr
GO
-----------------------------------------------------------------------------------------------------
-- 對左權限和右權限進行權限“或”運算
/*
PRINT ISNULL(ForBinary.PermissionOr(NULL, NULL), 0x0)
PRINT ISNULL(ForBinary.PermissionOr(0x123, NULL), 0x0)
PRINT ISNULL(ForBinary.PermissionOr(0x60, 0x06), 0x0)
*/
IF OBJECT_ID(N'ForBinary.PermissionOr') IS NOT NULL DROP FUNCTION ForBinary.PermissionOr
GO -- 注意!記得復制此兩行 SQL 語句到此腳本文件開始處,以便可以刪除程序集
CREATE FUNCTION ForBinary.PermissionOr -- 對左權限和右權限進行權限“或”運算
(
@left permission , -- 左權限
@right permission -- 右權限
)
RETURNS permission
AS EXTERNAL NAME Assemblies.[Assemblies.Functions.ScalarValued.ForBinary].OperationOr
GO
-----------------------------------------------------------------------------------------------------
-- 將所有分配給某公司職員的身份角色格式化成一個字符串。
/*
PRINT IdentityRole.FormatRolesOfEmployee(1, NULL, NULL)
PRINT IdentityRole.FormatRolesOfEmployee(1, '<a href="go.aspx?pkey={0}">{1}</a>', NULL)
*/
IF OBJECT_ID(N'IdentityRole.FormatRolesOfEmployee') IS NOT NULL DROP FUNCTION IdentityRole.FormatRolesOfEmployee
GO -- 注意!記得復制此兩行 SQL 語句到此腳本文件開始處,以便可以刪除程序集
CREATE FUNCTION IdentityRole.FormatRolesOfEmployee -- 將所有分配給某公司職員的身份角色格式化成一個字符串
(
@pkey int , -- 某公司職員的 PKey
@format nvarchar(max) , -- 轉換的格式字符串
@separator nvarchar(max) -- 多個身份角色之間的分隔符
)
RETURNS nvarchar(max)
AS EXTERNAL NAME Assemblies.[Assemblies.Functions.ScalarValued.IdentityRole].FormatRolesOfEmployee
GO
-----------------------------------------------------------------------------------------------------
-- 使用指定的格式和分隔符將由指定的 SQL 語句所生成的結果集連接成一個字符串。
/*
PRINT ForTable.JoinResultSet('select * from sys.tables', NULL, NULL)
PRINT ForTable.JoinResultSet('select * from TOperaPermiCategories', '{1} : {2}', char(13))
*/
IF OBJECT_ID(N'ForTable.JoinResultSet') IS NOT NULL DROP FUNCTION ForTable.JoinResultSet
GO -- 注意!記得復制此兩行 SQL 語句到此腳本文件開始處,以便可以刪除程序集
CREATE FUNCTION ForTable.JoinResultSet -- 使用指定的格式和分隔符將由指定的 SQL 語句所生成的結果集連接成一個字符串
(
@sql nvarchar(max) , -- 能生成結果集的 SQL 語句
@format nvarchar(max) , -- 連接時為結果集中每一條記錄所指定的格式
@separator nvarchar(max) -- 連接時每一條記錄之間的分隔符
)
RETURNS nvarchar(max)
AS EXTERNAL NAME Assemblies.[Assemblies.Functions.ScalarValued.ForTable].JoinResultSet
GO
-----------------------------------------------------------------------------------------------------
-- 使用指定的格式將由指定的 SQL 語句所生成的結果行連接成一個字符串。
/*
PRINT ForTable.JoinResultRow('select ''First'', ''Second'' ; ', NULL)
PRINT ForTable.JoinResultRow('select 0 , 1 , 2 , 3 ; ', '{1} : {2}')
*/
IF OBJECT_ID(N'ForTable.JoinResultRow') IS NOT NULL DROP FUNCTION ForTable.JoinResultRow
GO -- 注意!記得復制此兩行 SQL 語句到此腳本文件開始處,以便可以刪除程序集
CREATE FUNCTION ForTable.JoinResultRow -- 使用指定的格式將由指定的 SQL 語句所生成的結果行連接成一個字符串
(
@sql nvarchar(max) , -- 能生成結果行的 SQL 語句
@format nvarchar(max) -- 連接時所指定的格式
)
RETURNS nvarchar(max)
AS EXTERNAL NAME Assemblies.[Assemblies.Functions.ScalarValued.ForTable].JoinResultRow
GO
-----------------------------------------------------------------------------------------------------
-- 將指定的變體按指定的格式轉換為字符串。
/*
PRINT ForVariant.Format(1.2345, '000.0000000')
PRINT ForVariant.Format({d'2007-08-08'}, 'yyyy..MM..dd...')
*/
IF OBJECT_ID(N'ForVariant.Format') IS NOT NULL DROP FUNCTION ForVariant.Format
GO -- 注意!記得復制此兩行 SQL 語句到此腳本文件開始處,以便可以刪除程序集
CREATE FUNCTION ForVariant.Format -- 將指定的變體按指定的格式轉換為字符串
(
@variant sql_variant , -- 要進行轉換的變體對象
@format nvarchar(max) -- 轉換成字符串所使用的格式
)
RETURNS nvarchar(max)
AS EXTERNAL NAME Assemblies.[Assemblies.Functions.ScalarValued.ForVariant].Format
GO
-----------------------------------------------------------------------------------------------------
-- 檢測在指定的調入倉庫和調出倉庫之間是否存在有效的的產品調撥操作。
/*
PRINT StockpileRelated.HasValidProductDispatchingOperation(1, 2)
PRINT StockpileRelated.HasValidProductDispatchingOperation(4, 6)
*/
IF OBJECT_ID(N'StockpileRelated.HasValidProductDispatchingOperation') IS NOT NULL DROP FUNCTION StockpileRelated.HasValidProductDispatchingOperation
GO -- 注意!記得復制此兩行 SQL 語句到此腳本文件開始處,以便可以刪除程序集
CREATE FUNCTION StockpileRelated.HasValidProductDispatchingOperation -- 檢測在指定的調入倉庫和調出倉庫之間是否存在有效的的產品調撥操作
(
@dispatchIn int , -- 指定的調入倉庫的主鍵標識 PKey
@dispatchOut int -- 指定的調出倉庫的主鍵標識 PKey
)
RETURNS bit -- 如果在指定的調入倉庫和調出倉庫之間存在有效的的產品調撥操作則返回 true,否則返回 false
AS EXTERNAL NAME Assemblies.[Assemblies.Functions.ScalarValued.StockpileRelated].HasValidProductDispatchingOperation
GO
-----------------------------------------------------------------------------------------------------
-- 獲取所有對於指定的調入倉庫有效的調出倉庫。
/*
SELECT * FROM StockpileRelated.GetValidDispatchOutWarehouses(1)
SELECT * FROM StockpileRelated.GetValidDispatchOutWarehouses(6)
*/
IF OBJECT_ID(N'StockpileRelated.GetValidDispatchOutWarehouses') IS NOT NULL DROP FUNCTION StockpileRelated.GetValidDispatchOutWarehouses
GO -- 注意!記得復制此兩行 SQL 語句到此腳本文件開始處,以便可以刪除程序集
CREATE FUNCTION StockpileRelated.GetValidDispatchOutWarehouses -- 獲取所有對於指定的調入倉庫有效的調出倉庫
(
@dispatchIn int -- 指定的調入倉庫的主鍵標識 PKey
)
RETURNS TABLE (
[PKey] int
)
AS EXTERNAL NAME Assemblies.[Assemblies.Functions.TableValued.StockpileRelated].GetValidDispatchOutWarehouses
GO