每个用户使用SqlCacheDependency是个好主意吗?

时间:2012-04-24 16:49:40

标签: sql-server ado.net

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

查询看起来像这样

SELECT PermissionId, PermissionName From Permissions Where UserId = @UserId

这样我知道是否有任何记录改变,以清除该用户的缓存。

2 个答案:

答案 0 :(得分:5)

如果您阅读how Query Notifications work,您会明白为什么使用单个查询模板创建许多依赖项请求是一种很好的做法。对于您使用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通常在连接大量客户端时遇到问题(每个应用域连接阻止a worker thread):

  

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分钟),以下是单个插入的统计信息:

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

答案 1 :(得分:0)

文件说:

  

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

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

有几种选择:

  1. 每个表都有一个依赖项
  2. 每个表有100个依赖项,每行有一个依赖项。这应该是SQL Server可接受的数字,但您只需要使1%的缓存无效。
  3. 让触发器将所有更改行的ID输出到日志记录表中。在该表上创建依赖项并读取ID。这将告诉您确切的行已更改。
  4. 为了找出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状态。