是否有任何的多线程缓存机制,将在SQL CLR函数工作,而无需组件注册为“不安全”?
由于还介绍在这个职位 ,只需使用一个lock
声明将抛出一个安全组件的异常:
System.Security.HostProtectionException:
Attempted to perform an operation that was forbidden by the CLR host.
The protected resources (only available with full trust) were: All
The demanded resources were: Synchronization, ExternalThreading
我想我的功能的任何调用都使用相同的内部缓存,在一个线程安全的方式,使许多业务可以做缓存中读取,同时写入。 本质-我需要一个ConcurrentDictionary
,将在SQLCLR“安全”的组装工作。 不幸的是,使用ConcurrentDictionary
本身给出上述相同的异常。
有内置到SQLCLR或SQL Server来处理这个事情? 还是我误解SQLCLR的线程模型?
我已阅读,就像我可以找到有关SQLCLR的安全限制。 特别是,下面的文章可能是有用的明白我说的:
- SQL Server的CLR集成第1部分:安全
- 部署需要使用CLR和T-SQL不安全/外部访问/使用组件
这个代码最终会被分发到其他库的一部分,所以我真的不希望被要求运行为“不安全”。
我正在考虑(低于富豪在评论中长大的)一种选择是从SQLCLR代码中接触到的tempdb和使用,作为一个高速缓存来代替。 但我不太清楚究竟是如何做到这一点。 我也不能肯定这是否是为内存缓存就近高性能的任何地方。 见下面更新。
我感兴趣的可能是其他任何可用的替代品。 谢谢。
例
下面的代码使用静态辞典的并发作为高速缓存,并且访问经由SQL CLR用户定义函数缓存。 该函数的调用将使用相同的缓存工作。 但是,除非该组件注册为“不安全”这是不行的。
public class UserDefinedFunctions
{
private static readonly ConcurrentDictionary<string,string> Cache =
new ConcurrentDictionary<string, string>();
[SqlFunction]
public static SqlString GetFromCache(string key)
{
string value;
if (Cache.TryGetValue(key, out value))
return new SqlString(value);
return SqlString.Null;
}
[SqlProcedure]
public static void AddToCache(string key, string value)
{
Cache.TryAdd(key, value);
}
}
这些都是所谓的装配SqlClrTest
,并和使用下面的SQL包装:
CREATE FUNCTION [dbo].[GetFromCache](@key nvarchar(4000))
RETURNS nvarchar(4000) WITH EXECUTE AS CALLER
AS EXTERNAL NAME [SqlClrTest].[SqlClrTest.UserDefinedFunctions].[GetFromCache]
GO
CREATE PROCEDURE [dbo].[AddToCache](@key nvarchar(4000), @value nvarchar(4000))
WITH EXECUTE AS CALLER
AS EXTERNAL NAME [SqlClrTest].[SqlClrTest.UserDefinedFunctions].[AddToCache]
GO
然后,他们在这样的数据库中使用:
EXEC dbo.AddToCache 'foo', 'bar'
SELECT dbo.GetFromCache('foo')
UPDATE
我想通了如何从SQLCLR使用Access数据库上下文连接 。 该代码在此要点显示,无论是ConcurrentDictionary
方法和tempdb的方法。 然后我了一些测试,以从客户端统计信息(平均10次试验)测得的结果如下:
Concurrent Dictionary Cache
10,000 Writes: 363ms
10,000 Reads : 81ms
TempDB Cache
10,000 Writes: 3546ms
10,000 Reads : 1199ms
这样抛出使用tempdb数据库表的想法。 难道真的没有别的我可以试试吗?
Answer 1:
我添加了一个评论,说类似的东西,但我打算把它放在这里作为一个答案,而不是,因为我认为这可能需要一些背景知识。
ConcurrentDictionary
,因为你已经正确地指出,需要UNSAFE
最终,因为它使用线程同步原语,甚至超出了lock
-这明确要求获得更低级别的操作系统资源,因此需要在SQL托管环境之外的代码钓鱼。
所以,你可以不需要解决的唯一途径UNSAFE
,就是用一个不使用任何锁或其他线程同步原语。 但是,如果基础结构是一个.NET Dictionary
,然后在多个线程共享的唯一真正安全的方法是使用Lock
或Interlocked.CompareExchange
(见这里有自旋等待)。 我似乎无法找到对后者是否在所允许的任何信息SAFE
权限集,但我的猜测是,事实并非如此。
我也被质疑将基于CLR的解决了这个问题一个数据库引擎,它的转位和查找能力很可能会远远超过任何托管CLR解决方案内的有效性。
Answer 2:
接受的答案是不正确的。 Interlocked.CompareExchange
是不是一种选择,因为它需要一个共享的资源进行更新,并没有方法来创建表示静态变量,在SAFE
装配,可更新。
有(大部分)没有办法在一个缓存在调用数据SAFE
大会(也不应该有)。 其理由是,有类的单个实例(当然,在App域中其是每个数据库每所有者)是,在所有的会话共享。 这行为,更多的,往往不是非常不可取的。
不过,我确实说过“大部分”这是不可能的。 有一个办法,虽然我不知道这是否是一个错误或意图是这个样子。 我宁可它是一个错误,因为再次,共享变量在会话是一个非常危险的活动的一面。 然而,你可以(在你自己承担风险,这是没有具体线程安全的,但可能仍然工作)修改static readonly
集合 。 对。 如:
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.Collections;
public class CachingStuff
{
private static readonly Hashtable _KeyValuePairs = new Hashtable();
[SqlFunction(DataAccess = DataAccessKind.None, IsDeterministic = true)]
public static SqlString GetKVP(SqlString KeyToGet)
{
if (_KeyValuePairs.ContainsKey(KeyToGet.Value))
{
return _KeyValuePairs[KeyToGet.Value].ToString();
}
return SqlString.Null;
}
[SqlProcedure]
public static void SetKVP(SqlString KeyToSet, SqlString ValueToSet)
{
if (!_KeyValuePairs.ContainsKey(KeyToSet.Value))
{
_KeyValuePairs.Add(KeyToSet.Value, ValueToSet.Value);
}
return;
}
[SqlProcedure]
public static void UnsetKVP(SqlString KeyToUnset)
{
_KeyValuePairs.Remove(KeyToUnset.Value);
return;
}
}
而运行上面,以设置为数据库TRUSTWORTHY OFF
和大会设置为SAFE
,我们得到:
EXEC dbo.SetKVP 'f', 'sdfdg';
SELECT dbo.GetKVP('f'); -- sdfdg
SELECT dbo.GetKVP('g'); -- NULL
EXEC dbo.UnsetKVP 'f';
SELECT dbo.GetKVP('f'); -- NULL
这一切都这样说,有可能是不是一个更好的办法SAFE
也不会UNSAFE
。 因为欲望是使用内存因多次使用的值的缓存,为什么不建立一个分布式缓存或Redis的服务器并创建SQLCLR功能与它沟通? 这将只需要组件设置到EXTERNAL_ACCESS
。
这样,您就不必担心的几个问题:
Answer 3:
SQL Server的锁定功能sp_getapplock
和sp_releaseapplock
可以在安全的前提下使用。 聘请他们来保护一个普通的Dictionary
,你有自己的高速缓存!
锁定这样的价格比普通的差多了lock
,但如果你正在访问的相对粗细粒度方式缓存,可能不是一个问题。
--- UPDATE ---
该Interlocked.CompareExchange
可以包含在一个静态实例字段中。 静态参考可以由readonly
的,但在所引用对象的字段仍然可以是可变的,并且因此通过使用Interlocked.CompareExchange
。
无论Interlocked.CompareExchange
和static readonly
被允许在安全的前提下。 性能大大优于sp_getapplock
。
Answer 4:
基于安德拉斯答案,这是我的一个“SharedCache”植入阅读和在安全许可的字典写。
EvalManager(静态)
using System;
using System.Collections.Generic;
using Z.Expressions.SqlServer.Eval;
namespace Z.Expressions
{
/// <summary>Manager class for eval.</summary>
public static class EvalManager
{
/// <summary>The cache for EvalDelegate.</summary>
public static readonly SharedCache<string, EvalDelegate> CacheDelegate = new SharedCache<string, EvalDelegate>();
/// <summary>The cache for SQLNETItem.</summary>
public static readonly SharedCache<string, SQLNETItem> CacheItem = new SharedCache<string, SQLNETItem>();
/// <summary>The shared lock.</summary>
public static readonly SharedLock SharedLock;
static EvalManager()
{
// ENSURE to create lock first
SharedLock = new SharedLock();
}
}
}
SharedLock
using System.Threading;
namespace Z.Expressions.SqlServer.Eval
{
/// <summary>A shared lock.</summary>
public class SharedLock
{
/// <summary>Acquires the lock on the specified lockValue.</summary>
/// <param name="lockValue">[in,out] The lock value.</param>
public static void AcquireLock(ref int lockValue)
{
do
{
// TODO: it's possible to wait 10 ticks? Thread.Sleep doesn't really support it.
} while (0 != Interlocked.CompareExchange(ref lockValue, 1, 0));
}
/// <summary>Releases the lock on the specified lockValue.</summary>
/// <param name="lockValue">[in,out] The lock value.</param>
public static void ReleaseLock(ref int lockValue)
{
Interlocked.CompareExchange(ref lockValue, 0, 1);
}
/// <summary>Attempts to acquire lock on the specified lockvalue.</summary>
/// <param name="lockValue">[in,out] The lock value.</param>
/// <returns>true if it succeeds, false if it fails.</returns>
public static bool TryAcquireLock(ref int lockValue)
{
return 0 == Interlocked.CompareExchange(ref lockValue, 1, 0);
}
}
}
SharedCache
using System;
using System.Collections.Generic;
namespace Z.Expressions.SqlServer.Eval
{
/// <summary>A shared cache.</summary>
/// <typeparam name="TKey">Type of key.</typeparam>
/// <typeparam name="TValue">Type of value.</typeparam>
public class SharedCache<TKey, TValue>
{
/// <summary>The lock value.</summary>
public int LockValue;
/// <summary>Default constructor.</summary>
public SharedCache()
{
InnerDictionary = new Dictionary<TKey, TValue>();
}
/// <summary>Gets the number of items cached.</summary>
/// <value>The number of items cached.</value>
public int Count
{
get { return InnerDictionary.Count; }
}
/// <summary>Gets or sets the inner dictionary used to cache items.</summary>
/// <value>The inner dictionary used to cache items.</value>
public Dictionary<TKey, TValue> InnerDictionary { get; set; }
/// <summary>Acquires the lock on the shared cache.</summary>
public void AcquireLock()
{
SharedLock.AcquireLock(ref LockValue);
}
/// <summary>Adds or updates a cache value for the specified key.</summary>
/// <param name="key">The cache key.</param>
/// <param name="value">The cache value used to add.</param>
/// <param name="updateValueFactory">The cache value factory used to update.</param>
/// <returns>The value added or updated in the cache for the specified key.</returns>
public TValue AddOrUpdate(TKey key, TValue value, Func<TKey, TValue, TValue> updateValueFactory)
{
try
{
AcquireLock();
TValue oldValue;
if (InnerDictionary.TryGetValue(key, out oldValue))
{
value = updateValueFactory(key, oldValue);
InnerDictionary[key] = value;
}
else
{
InnerDictionary.Add(key, value);
}
return value;
}
finally
{
ReleaseLock();
}
}
/// <summary>Adds or update a cache value for the specified key.</summary>
/// <param name="key">The cache key.</param>
/// <param name="addValueFactory">The cache value factory used to add.</param>
/// <param name="updateValueFactory">The cache value factory used to update.</param>
/// <returns>The value added or updated in the cache for the specified key.</returns>
public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
{
try
{
AcquireLock();
TValue value;
TValue oldValue;
if (InnerDictionary.TryGetValue(key, out oldValue))
{
value = updateValueFactory(key, oldValue);
InnerDictionary[key] = value;
}
else
{
value = addValueFactory(key);
InnerDictionary.Add(key, value);
}
return value;
}
finally
{
ReleaseLock();
}
}
/// <summary>Clears all cached items.</summary>
public void Clear()
{
try
{
AcquireLock();
InnerDictionary.Clear();
}
finally
{
ReleaseLock();
}
}
/// <summary>Releases the lock on the shared cache.</summary>
public void ReleaseLock()
{
SharedLock.ReleaseLock(ref LockValue);
}
/// <summary>Attempts to add a value in the shared cache for the specified key.</summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
/// <returns>true if it succeeds, false if it fails.</returns>
public bool TryAdd(TKey key, TValue value)
{
try
{
AcquireLock();
if (!InnerDictionary.ContainsKey(key))
{
InnerDictionary.Add(key, value);
}
return true;
}
finally
{
ReleaseLock();
}
}
/// <summary>Attempts to remove a key from the shared cache.</summary>
/// <param name="key">The key.</param>
/// <param name="value">[out] The value.</param>
/// <returns>true if it succeeds, false if it fails.</returns>
public bool TryRemove(TKey key, out TValue value)
{
try
{
AcquireLock();
var isRemoved = InnerDictionary.TryGetValue(key, out value);
if (isRemoved)
{
InnerDictionary.Remove(key);
}
return isRemoved;
}
finally
{
ReleaseLock();
}
}
/// <summary>Attempts to get value from the shared cache for the specified key.</summary>
/// <param name="key">The key.</param>
/// <param name="value">[out] The value.</param>
/// <returns>true if it succeeds, false if it fails.</returns>
public bool TryGetValue(TKey key, out TValue value)
{
try
{
return InnerDictionary.TryGetValue(key, out value);
}
catch (Exception)
{
value = default(TValue);
return false;
}
}
}
}
源文件:
https://github.com/zzzprojects/Eval-SQL.NET/blob/master/src/Z.Expressions.SqlServer.Eval/EvalManager/EvalManager.cs
https://github.com/zzzprojects/Eval-SQL.NET/blob/master/src/Z.Expressions.SqlServer.Eval/Shared/SharedLock.cs
https://github.com/zzzprojects/Eval-SQL.NET/blob/master/src/Z.Expressions.SqlServer.Eval/Shared/SharedCache.cs
Answer 5:
将您的需求得到满足与表变量? 他们保存在内存中,只要有可能,无论如何,这样的表现应该是优秀的。 若您需要维护应用程序调用之间缓存,当然非常有用。
创建为一个类型,你也可以通过这样的表到一个存储过程或者UDF。
文章来源: Multithreaded caching in SQL CLR