SQLCLR中的多线程缓存有没有什么多线程缓存机制可以在SQLCLR函数中工作而不用将程序集注册为“不安全”?如本文所述,仅使用lock语句会在安全程序集上引发异常:System.Security.HostProtectionException:试图执行CLR主机禁止的操作。受保护的资源(仅在完全信任的情况下可用)是:所有所需的资源是:同步,外部线程我希望对函数的任何调用以线程安全的方式使用相同的内部缓存,以便许多操作可以执行缓存读取和同时写入。本质上-我需要一个可以在SQLCLR“安全”程序集中使用的ConcurrentDictionary。不幸的是,使用ConcurrentDictionary本身会产生与上述相同的异常。是否有内置的SQLCLR或SQLServer来处理这个问题?还是我误解了SQLCLR的线程模型?我读过SQLCLR的安全限制。特别是,以下文章可能有助于理解我在说什么:此代码最终将成为分发给其他人的库的一部分,因此我真的不想将其作为“不安全”运行。我正在考虑的一个选项(由Spender在下面的评论中建议)是从SQLCLR代码中扩展到tempdb并将其用作缓存。但我不太确定该怎么做。我也不确定它会像memcache一样高效。请参阅下面的更新我对可能可用的任何其他替代方案感兴趣。谢谢。示例以下代码使用静态并发字典作为缓存,并通过SQLCLR用户定义函数访问缓存。对该函数的所有调用都将使用相同的缓存。但这将不起作用,除非程序集被注册为“不安全”。公共类UserDefinedFunctions{privatestaticreadonlyConcurrentDictionaryCache=newConcurrentDictionary();[SqlFunction]publicstaticSqlStringGetFromCache(stringkey){字符串值;如果(Cache.TryGetValue(key,outvalue))returnnewSqlString(value);返回SqlString.Null;}[SqlProcedure]publicstaticvoidAddToCache(stringkey,stringvalue){Cache.TryAdd(key,value);它们位于名为SqlClrTest的过程中,称为SqlClrTest并使用以下SQL包装器:CREATEFUNCTION[dbo].[GetFromCache](@keynvarchar(4000))RETURNSnvarchar(4000)WITHEXECUTEASCALLERASEXTERNALNAME[SqlClrTest].[SqlClrTest.UserDefinedFunctions].[GetFromCache]GOCREATEPROCEDURE[dbo].[AddToCache](@keynvarchar(4000),@valuenvarchar(4000))WITHEXECUTEASCALLERASEXTERNALNAME[SqlClrTest].[SqlClrTest.UserDefinedFunctions]].[AddToCache]GO然后它们在数据库中的使用如下:EXECdbo.AddToCache'foo','bar'SELECTdbo.GetFromCache('foo')UPDATE我想出了如何使用上下文连接从SQLCLR访问数据库.此Gist中的代码显示了ConcurrentDictionary方法和tempdb方法。然后我运行了一些测试并从客户端统计数据中测量了以下结果(10次试验的平均值):并发字典缓存10,000次写入:363毫秒10,000次读取:81毫秒TempDB缓存10,000次写入:3546毫秒10,000次读取:1199毫秒所以使用tempdb表的想法。我真的无能为力了吗?我添加了类似的评论,但我会把它放在这里作为答案,因为我认为它可能需要一些背景知识。正如您正确指出的那样,ConcurrentDictionary最终需要是不安全的,因为它甚至使用了超出锁定线程同步原语的方法——这显然需要访问较低级别的操作系统资源,因此需要在SQL托管环境之外捕获代码。因此,获得不需要UNSAFE的解决方案的唯一方法是使用不使用任何锁或其他线程同步原语的解决方案。但是,如果底层结构是.Net字典,那么在多个线程之间共享它的唯一真正安全的方法是使用Lock或Interlocked.CompareExchange(参见此处)和自旋等待。我似乎无法找到关于后者是否在SAFE权限集下被允许的任何信息,但我的猜测是它不是。我还质疑在数据库引擎中应用基于CLR的解决方案的有效性,其索引和查找功能可能远远超过任何托管CLR解决方案。接受的答案不正确。Interlocked.CompareExchange不是一个选项,因为它需要更新共享资源,并且无法在可以更新的SAFE程序集中创建所述静态变量。(在大多数情况下)没有办法在SAFE程序集中的调用之间缓存数据(也不应该)。原因是此类的一个实例在所有会话之间共享(在AppDomain中,每个数据库每个所有者)。这种行为通常是非常不受欢迎的。但是,我确实说过“大部分时间”,这是不可能的。有一种方法,但我不确定这是一个错误还是故意的。我会错误地认为它是一个错误,因为再次跨会话共享变量是一个非常不稳定的活动。尽管如此,您可以(这样做需要您自担风险,这不是特定于线程安全的,但可能仍然有效)修改静态只读集合。正确的。如:使用Microsoft.SqlServer.Server;使用System.Data.SqlTypes;使用系统集合;公共类CachingStuff{privatestaticreadonlyHashtable_KeyValuePairs=newHashtable();[SqlFunction(DataAccess=DataAccessKind.None,IsDeterministic=true)]publicstaticSqlStringGetKVP(SqlStringKeyToGet){如果(_KeyValuePairs.ContainsKey(KeyToGet.Value)){返回_KeyValuePairs[KeyToGet.Value].ToString();}返回SqlString.Null;}[SqlProcedure]publicstaticvoidSetKVP(SqlStringKeyToSet,SqlStringValueToSet){if(!_KeyValuePairs.ContainsKey(KeyToSet.Value)){_KeyValuePairs.Add(KeyToSet.Value,ValueToSet.Value);}返回;}[SqlProcedure]publicstaticvoidUnsetKVP(SqlStringKeyToUnset){_KeyValuePairs.Remove(KeyToUnset.Value);返回;在数据库设置为TRUSTWORTHYOFF并将程序集设置为SAFE的情况下运行上面的代码,我们得到:EXECdbo.SetKVP'f','sdfdg';选择dbo.GetKVP('f');--sdfdg选择dbo。GetKVP('g');--空执行dbo。取消设置KVP'f';选择dbo。GetKVP('f');--NULL不过,可能还有一种既不安全也不不安全的更好方法既然你想使用内存来缓存重复使用的值,为什么不设置一个memcached或redis服务器并创建SQLCLR函数来与之通信呢?这只需要将程序集设置为EXTERNAL_ACCESS。这样您就不必担心几个问题:SQLServer锁定函数sp_getapplock和sp_releaseapplock可以在SAFE上下文中使用。雇用他们来保护普通词典,你自己就有了一个缓存!以这种方式加锁的代价比普通锁差很多,但如果你以一种相对粗粒度的方式访问缓存,这可能不是问题。--更新--Interlocked.CompareExchange可用于静态实例中包含的字段。静态引用可以是只读的,但是被引用对象中的字段仍然是可变的,因此Interlocked.CompareExchange可以使用它。在SAFE上下文中允许Interlocked.CompareExchange和静态只读。性能比sp_getapplock好得多。根据Andras的回答,这是我在具有SAFE权限的字典中读写的“SharedCache”。EvalManager(静态)使用系统;使用System.Collections.Generic;使用Z.Expressions.SqlServer.Eval;namespaceZ.Expressions{///eval的管理器类。publicstaticclassEvalManager{///EvalDelegate的缓存。publicstaticreadonlySharedCacheCacheDelegate=newSharedCache();///SQLNETItem的缓存。publicstaticreadonlySharedCacheCacheItem=newSharedCache();///共享锁。publicstaticreadonlySharedLock共享锁;staticEvalManager(){//确保首先创建锁SharedLock=newSharedLock();}}}SharedLock使用System.Threading;namespaceZ.Expressions.SqlServer.Eval{///共享锁。publicclassSharedLock{///获取指定lockValue上的锁。///[in,out]锁值。publicstaticvoidAcquireLock(refintlockValue){do{//TODO:可以等待10个滴答吗?Thread.Sleep并不真正支持它。}while(0!=Interlocked.CompareExchange(reflockValue,1,0));}///释放指定lockValue上的锁。///[in,out]锁值。publicstaticvoidReleaseLock(refintlockValue){Interlocked.CompareExchange(reflockValue,0,1);}///尝试获取指定锁值的锁。///[in,out]锁值。///成功则为真,失败则为假。publicstaticboolTryAcquireLock(refintlockValue){返回0==Interlocked.CompareExchange(reflockValue,1,0);}}}SharedCache使用系统;使用System.Collections.Generic;namespaceZ.Expressions.SqlServer.Eval{///共享缓存。///键的类型。///值的类型。publicclassSharedCache{///锁值。公共int锁值;///默认构造函数。publicSharedCache(){InnerDictionary=newDictionary();}///获取缓存的项目数。///缓存的项目数。publicintCount{get{返回InnerDictionary.Count;}}///获取或设置用于缓存项的内部字典。///内部字典used缓存项目。publicDictionaryInnerDictionary{get;放;}///获取共享缓存的锁。publicvoidAcquireLock(){SharedLock.AcquireLock(refLockValue);}///添加或更新指定键的缓存值。///缓存键。///用于添加的缓存值。///用于更新的缓存值工厂。///在缓存中为指定键添加或更新的值。publicTValueAddOrUpdate(TKeykey,TValuevalue,FuncupdateValueFactory){try{AcquireLock();TValue旧值;如果(InnerDictionary.TryGetValue(key,outoldValue)){value=updateValueFactory(key,oldValue);InnerDictionary[键]=值;}else{InnerDictionary.Add(key,value);}返回值;}最后{ReleaseLock();}}///添加或更新指定键的缓存值。///缓存键。///用于添加的缓存值工厂。///用于更新的缓存值工厂。///在缓存中为指定键添加或更新的值。浦blicTValueAddOrUpdate(TKeykey,FuncaddValueFactory,FuncupdateValueFactory){try{AcquireLock();TValue值;TValue旧值;如果(InnerDictionary.TryGetValue(key,outoldValue)){value=updateValueFactory(key,oldValue);InnerDictionary[键]=值;}else{value=addValueFactory(key);InnerDictionary.Add(键,值);}返回值;}最后{ReleaseLock();}}///清除所有缓存项。publicvoidClear(){尝试{AcquireLock();InnerDictionary.Clear();}最后{ReleaseLock();}}///释放共享缓存上的锁。publicvoidReleaseLock(){SharedLock.ReleaseLock(refLockValue);}///尝试在共享缓存中为指定键添加一个值。///钥匙。///价值。///成功则为真,失败则为假。publicboolTryAdd(TKeykey,TValuevalue){try{AcquireLock();如果(!InnerDictionary.ContainsKey(key)){InnerDictionary.Add(key,value);}返回真;}最后{ReleaseLock();}}///尝试ts从共享缓存中删除一个键。///钥匙。///[输出]值。///成功则为真,失败则为假。publicboolTryRemove(TKeykey,outTValuevalue){try{AcquireLock();varisRemoved=InnerDictionary.TryGetValue(key,outvalue);如果(isRemoved){InnerDictionary.Remove(键);}返回已移除;}最后{ReleaseLock();}}///尝试从指定键的共享缓存中获取值。///钥匙。///[输出]值。///成功则为真,失败则为假。publicboolTryGetValue(TKeykey,outTValuevalue){try{returnInnerDictionary.TryGetValue(key,outvalue);}}catch(Exception){value=default(TValue);返回假;}}}}源文件:表变量满足你的需求吗?尽管它们保留在内存中,因此性能应该非常出色。当然,如果您需要在应用程序调用之间维护缓存,就没那么有用了。作为类型创建,您还可以将此类表传递给存储过程或UDF。以上就是C#学习教程:SQLCLR中的多线程缓存分享的全部内容。如果对你有用,需要进一步了解C#学习教程,希望大家多多关注。本文收集自网络,不代表立场。如涉及侵权请点击右侧联系管理员删除。如需转载请注明出处:
