0

我正在考虑为我们的应用程序服务器上的每个用户缓存权限。SqlCacheDependency为每个用户使用 a 是个好主意吗?

查询看起来像这样

SELECT PermissionId, PermissionName From Permissions Where UserId = @UserId

这样我就知道这些记录中是否有任何更改,然后为该用户清除我的缓存。

4

2 回答 2

5

如果您阅读了查询通知的工作原理,您就会明白为什么使用单个查询模板创建多个依赖项请求是一种很好的做法。SqlCacheDependency对于一个网络应用程序,这是由你使用和不使用的事实所暗示的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 或中间层服务,在这些服务中,相对少量的服务器对数据库具有活动的依赖项。它不是为在客户端应用程序中使用而设计的,在客户端应用程序中,成百上千的客户端计算机将为单个数据库服务器设置 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);

        }

    }
}

在设置了 50k 订阅后(大约需要 5 分钟),这里是单个插入的 stats io:

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
于 2012-04-24T17:15:48.593 回答
0

文档说:

SqlDependency 设计用于 ASP.NET 或中间层服务,在这些服务中,相对少量的服务器对数据库具有活动的依赖项。它不是为在客户端应用程序中使用而设计的,在客户端应用程序中,成百上千的客户端 计算机将为单个数据库服务器设置 SqlDependency 对象。

它告诉我们不要打开数千个缓存依赖项。这可能会导致 SQL Server 上的资源问题。

有几种选择:

  1. 每个表都有一个依赖项
  2. 每个表有 100 个依赖项,每百分比的行一个。这对于 SQL Server 来说应该是一个可接受的数字,但您只需要使 1% 的缓存无效。
  3. 让触发器将所有更改行的 ID 输出到日志记录表中。在该表上创建一个依赖项并读取 ID。这将准确地告诉您哪些行已更改。

为了确定 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。

于 2012-04-24T17:22:19.020 回答