C# sqlclr中的多线程缓存
是否有任何多线程缓存机制可以在SQL CLR函数中工作,而无需将程序集注册为“不安全” 如前所述,仅使用C# sqlclr中的多线程缓存,c#,sql-server,multithreading,caching,sqlclr,C#,Sql Server,Multithreading,Caching,Sqlclr,是否有任何多线程缓存机制可以在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: Al
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
我希望对我的函数的任何调用都以线程安全的方式使用相同的内部缓存,以便许多操作可以同时进行缓存读写。本质上-我需要一个能在SQLCLR“安全”程序集中工作的ConcurrentDictionary
。不幸的是,使用ConcurrentDictionary
本身会产生与上述相同的异常
SQLCLR或SQL Server中是否内置了一些东西来处理此问题?还是我误解了SQLCLR的线程模型
我已经尽可能多地阅读了SQLCLR的安全限制。特别是,以下文章可能有助于理解我所说的内容:
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;
}
}
}
}
下面的代码使用静态并发字典作为缓存,并通过SQL CLR用户定义函数访问该缓存。对函数的所有调用都将使用相同的缓存。但是,除非程序集注册为“不安全”,否则这将不起作用
公共类用户定义函数
{
私有静态只读ConcurrentDictionary缓存=
新的ConcurrentDictionary();
[SqlFunction]
公共静态SqlString GetFromCache(字符串键)
{
字符串值;
if(Cache.TryGetValue(key,out值))
返回新的SqlString(值);
返回SqlString.Null;
}
[SqlProcedure]
公共静态void AddToCache(字符串键、字符串值)
{
Cache.TryAdd(键,值);
}
}
它们位于名为SqlClrTest
的程序集中,并使用以下SQL包装器:
创建函数[dbo].[GetFromCache](@key-nvarchar(4000))
返回nvarchar(4000)和EXECUTE作为调用者
作为外部名称[SqlClrTest]。[SqlClrTest.UserDefinedFunctions]。[GetFromCache]
去
创建过程[dbo]。[AddToCache](@key-nvarchar(4000),@value-nvarchar(4000))
以EXECUTE作为调用方
作为外部名称[SqlClrTest]。[SqlClrTest.UserDefinedFunctions]。[AddToCache]
去
然后在数据库中使用它们,如下所示:
EXEC dbo.AddToCache'foo','bar'
选择dbo.GetFromCache('foo')
更新
我找到了如何使用从SQLCLR访问数据库的方法。代码同时显示了ConcurrentDictionary
方法和tempdb方法。然后,我进行了一些测试,根据客户统计数据(平均10次试验)得出以下结果:
所以这就抛弃了使用tempdb表的想法。真的没有别的我可以尝试的了吗?我添加了一条类似的评论,但我将把它放在这里作为一个答案,因为我认为它可能需要一些背景知识 正如您正确指出的那样,
ConcurrentDictionary
,最终要求不安全
,因为它使用的线程同步原语甚至超过了锁
——这明确要求访问较低级别的操作系统资源,因此需要在SQL宿主环境之外进行代码捕获
因此,获得不需要不安全的解决方案的唯一方法是使用不使用任何锁或其他线程同步原语的解决方案。但是,如果底层结构是.Net字典
,那么跨多个线程共享它的唯一真正安全的方法是使用锁
或互锁。将交换
(请参阅)与旋转等待进行比较。我似乎找不到任何关于SAFE
权限集是否允许后者的信息,但我猜不是
我还质疑在数据库引擎中应用基于CLR的解决方案来解决这个问题的有效性,因为数据库引擎的索引和查找功能可能远远超过任何托管的CLR解决方案。使用表变量可以满足您的需要吗?它们被尽可能长地保存在内存中,所以性能应该非常好。当然,如果你需要在应用程序调用之间维护缓存,那么就没那么有用了
创建为类型,也可以将此类表传递到存储过程或UDF。接受的答案不正确<代码>互锁。CompareExchange不是一个选项,因为它需要一个共享资源来更新,并且无法在
安全的程序集中创建可以更新的静态变量
在SAFE
程序集中,没有(大部分情况下)方法跨调用缓存数据(也不应该有)。原因是该类只有一个实例(在应用程序域中,每个数据库每个所有者)在所有会话中共享。这种行为通常是非常不受欢迎的
然而,我确实说过“在很大程度上”这是不可能的。有一种方法,尽管我不确定这是一个bug还是打算这样做。我认为这是一个错误,因为在会话间共享变量是一项非常不稳定的活动。尽管如此,您仍然可以修改静态只读
集合(这样做的风险由您自己承担,这并不是线程安全的,但可能仍然有效)。是的。例如:
使用微软
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();
}
}
}
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);
}
}
}
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;
}
}
}
}