14

アセンブリを「安全でない」として登録する必要なく、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 のセキュリティ制限については、できる限り読みました。特に、次の記事は、私が話していることを理解するのに役立つ場合があります。

このコードは最終的に他の人に配布されるライブラリの一部になるので、「安全でない」として実行する必要はありません。

私が検討している 1 つのオプション (以下の Spender によるコメントで取り上げられています) は、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')

アップデート

Context Connectionを使用して SQLCLR からデータベースにアクセスする方法を理解しました。この Gistのコードは、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 テーブルを使用するという考えは破棄されます。私が試すことができるものは他に本当にありますか?

4

5 に答える 5

1

SQL Server のロック機能sp_getapplockでありsp_releaseapplock、SAFE コンテキストで使用できます。それらを使用して普通の人を保護Dictionaryすれば、キャッシュを手に入れることができます!

この方法でロックする代償は、通常の方法よりもはるかに悪いlockですが、比較的粗い方法でキャッシュにアクセスしている場合は問題にならない可能性があります。

- - アップデート - -

Interlocked.CompareExchange、静的インスタンスに含まれるフィールドで使用できます。静的参照を行うことはできますがreadonly、参照されるオブジェクトのフィールドは依然として変更可能であり、したがって で使用できInterlocked.CompareExchangeます。

Interlocked.CompareExchangeSAFE コンテキストではとの両方static readonlyが許可されます。よりもはるかに優れたパフォーマンスを発揮しsp_getapplockます。

于 2015-07-16T20:42:10.933 に答える
1

Andrasの回答に基づいて、 SAFE権限で辞書を読み書きするための「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();
        }
    }
}

共有ロック

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;
            }
        }
    }
}

ソース ファイル:

于 2016-02-02T17:18:11.233 に答える