アプリケーションサーバー上のすべてのユーザーのアクセス許可をキャッシュすることを考えています。すべてのユーザーに を使用することをお勧めしSqlCacheDependency
ますか?
クエリは次のようになります
SELECT PermissionId, PermissionName From Permissions Where UserId = @UserId
そうすれば、それらのレコードのいずれかが変更された場合に、そのユーザーのキャッシュを消去することがわかります。
アプリケーションサーバー上のすべてのユーザーのアクセス許可をキャッシュすることを考えています。すべてのユーザーに を使用することをお勧めしSqlCacheDependency
ますか?
クエリは次のようになります
SELECT PermissionId, PermissionName From Permissions Where UserId = @UserId
そうすれば、それらのレコードのいずれかが変更された場合に、そのユーザーのキャッシュを消去することがわかります。
クエリ通知のしくみを読めば、1 つのクエリ テンプレートで多くの依存関係要求を作成することがなぜ良い方法なのかがわかります。SqlCacheDependency
を使用していて を使用していないという事実によって暗示される Web アプリの場合、SqlDependency
実行する予定は問題ないはずです。Linq2Sql を使用している場合は、LinqToCache を試すこともできます。
var queryUsers = from u in repository.Users
where u.UserId = currentUserId
select u;
var user= queryUsers .AsCached("Users:" + currentUserId.ToString());
ファット クライアント アプリの場合、それは問題です。クエリ自体が原因SqlDependency
ではありませんが、一般に、多数のクライアントが接続されていると問題が発生するためです (接続されているアプリ ドメインごとにワーカー スレッドがブロックされます)。
SqlDependencyは、データベースに対してアクティブな依存関係を持つ比較的少数のサーバーが存在する ASP.NET または中間層サービスで使用するように設計されています。これは、数百または数千のクライアント コンピューターが 1 つのデータベース サーバーに対してセットアップされた SqlDependency オブジェクトを持つクライアント アプリケーションで使用するようには設計されていません。
更新しました
これは、@usr が彼の投稿で行ったのと同じテストです。完全な C# コード:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using DependencyMassTest.Properties;
using System.Threading.Tasks;
using System.Threading;
namespace DependencyMassTest
{
class Program
{
static volatile int goal = 50000;
static volatile int running = 0;
static volatile int notified = 0;
static int workers = 50;
static SqlConnectionStringBuilder scsb;
static AutoResetEvent done = new AutoResetEvent(false);
static void Main(string[] args)
{
scsb = new SqlConnectionStringBuilder(Settings.Default.ConnString);
scsb.AsynchronousProcessing = true;
scsb.Pooling = true;
try
{
SqlDependency.Start(scsb.ConnectionString);
using (var conn = new SqlConnection(scsb.ConnectionString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(@"
if object_id('SqlDependencyTest') is not null
drop table SqlDependencyTest
create table SqlDependencyTest (
ID int not null identity,
SomeValue nvarchar(400),
primary key(ID)
)
", conn))
{
cmd.ExecuteNonQuery();
}
}
for (int i = 0; i < workers; ++i)
{
Task.Factory.StartNew(
() =>
{
RunTask();
});
}
done.WaitOne();
Console.WriteLine("All dependencies subscribed. Waiting...");
Console.ReadKey();
}
catch (Exception e)
{
Console.Error.WriteLine(e);
}
finally
{
SqlDependency.Stop(scsb.ConnectionString);
}
}
static void RunTask()
{
Random rand = new Random();
SqlConnection conn = new SqlConnection(scsb.ConnectionString);
conn.Open();
SqlCommand cmd = new SqlCommand(
@"select SomeValue
from dbo.SqlDependencyTest
where ID = @id", conn);
cmd.Parameters.AddWithValue("@id", rand.Next(50000));
SqlDependency dep = new SqlDependency(cmd);
dep.OnChange += new OnChangeEventHandler((ob, qnArgs) =>
{
Console.WriteLine("Notified {3}: Info:{0}, Source:{1}, Type:{2}", qnArgs.Info, qnArgs.Source, qnArgs.Type, Interlocked.Increment(ref notified));
});
cmd.BeginExecuteReader(
(ar) =>
{
try
{
int crt = Interlocked.Increment(ref running);
if (crt % 1000 == 0)
{
Console.WriteLine("{0} running...", crt);
}
using (SqlDataReader rdr = cmd.EndExecuteReader(ar))
{
while (rdr.Read())
{
}
}
}
catch (Exception e)
{
Console.Error.WriteLine(e.Message);
}
finally
{
conn.Close();
int left = Interlocked.Decrement(ref goal);
if (0 == left)
{
done.Set();
}
else if (left > 0)
{
RunTask();
}
}
}, null);
}
}
}
50,000 のサブスクリプションが設定された後 (約 5 分かかります)、単一の挿入の統計情報は次のとおりです。
set statistics time on
insert into Test..SqlDependencyTest (SomeValue) values ('Foo');
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.
SQL Server Execution Times:
CPU time = 16 ms, elapsed time = 16 ms.
1000 行の挿入には約 7 秒かかり、これには数百の通知の起動が含まれます。CPU 使用率は約 11% です。これはすべて私の T420s ThinkPad にあります。
set nocount on;
go
begin transaction
go
insert into Test..SqlDependencyTest (SomeValue) values ('Foo');
go 1000
commit
go
ドキュメントには次のように書かれています:
SqlDependency は、データベースに対してアクティブな依存関係を持つ比較的少数のサーバーが存在する ASP.NET または中間層サービスで使用するように設計されています。これは、数百または数千のクライアント コンピューターが単一のデータベース サーバーに対して SqlDependency オブジェクトをセットアップするようなクライアント アプリケーションで使用するようには設計されていません。
何千ものキャッシュの依存関係を開いてはいけません。これにより、SQL Server でリソースの問題が発生する可能性があります。
いくつかの代替手段があります。
SqlDependency が大規模な使用に適しているかどうかを確認するために、ベンチマークを行いました。
static void SqlDependencyMassTest()
{
var connectionString = "Data Source=(local); Initial Catalog=Test; Integrated Security=true;";
using (var dependencyConnection = new SqlConnection(connectionString))
{
dependencyConnection.EnsureIsOpen();
dependencyConnection.ExecuteNonQuery(@"
if object_id('SqlDependencyTest') is not null
drop table SqlDependencyTest
create table SqlDependencyTest (
ID int not null identity,
SomeValue nvarchar(400),
primary key(ID)
)
--ALTER DATABASE Test SET ENABLE_BROKER with rollback immediate
");
SqlDependency.Start(connectionString);
for (int i = 0; i < 1000 * 1000; i++)
{
using (var sqlCommand = new SqlCommand("select ID from dbo.SqlDependencyTest where ID = @id", dependencyConnection))
{
sqlCommand.AddCommandParameters(new { id = StaticRandom.ThreadLocal.GetInt32() });
CreateSqlDependency(sqlCommand, args =>
{
});
}
if (i % 1000 == 0)
Console.WriteLine(i);
}
}
}
コンソールをスクロールすると、作成された依存関係の量を確認できます。それは非常に急速に遅くなります。ポイントを証明する必要がなかったので、正式な測定は行いませんでした.
また、テーブルへの単純な挿入の実行計画は、コストの 99% が 50k の依存関係の維持に関連していることを示しています。
結論: 本番環境ではまったく機能しません。30 分後、55k の依存関係が作成されました。常に 100% の CPU でマシンを実行します。