在SQL CLR线程缓存(Multithreaded caching in SQL CLR)

2019-09-01 12:41发布

是否有任何的多线程缓存机制,将在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 ,然后在多个线程共享的唯一真正安全的方法是使用LockInterlocked.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

这样,您就不必担心的几个问题:

  • 消耗一堆存储器可能/应该用于查询。

  • 还有在静态变量中保存的数据不会自动到期。 它的存在,直到将其删除或应用程序域被卸载,这可能不会发生很长一段时间。 但是,MemcachedRedis的也允许设置过期时间。

  • 这是没有明确线程安全的。 但是,缓存服务器。



Answer 3:

SQL Server的锁定功能sp_getapplocksp_releaseapplock可以在安全的前提下使用。 聘请他们来保护一个普通的Dictionary ,你有自己的高速缓存!

锁定这样的价格比普通的差多了lock ,但如果你正在访问的相对粗细粒度方式缓存,可能不是一个问题。

--- UPDATE ---

Interlocked.CompareExchange可以包含在一个静态实例字段中。 静态参考可以由readonly的,但在所引用对象的字段仍然可以是可变的,并且因此通过使用Interlocked.CompareExchange

无论Interlocked.CompareExchangestatic 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