文章編號: 889696 - 上次校閱: 2007年11月2日 - 版次: 1.3

當許多資料庫存在於 SQL Server 執行個體時,可能會遇到緩慢回應從 SQL Server 企業管理員

系統提示本文適用於您使用的作業系統之外的作業系統。與您不相關的文章內容已停用。
全部展開 | 全部摺疊

徵狀

當許多資料庫存在預設執行個體或具名執行個體的 Microsoft SQL Server 中時,可能會遇到緩慢回應從 [SQL Server 企業管理員] 中,切入 [資料庫] 資料夾時,特別是。如果 [資料庫] 資料夾中包含多個一千資料庫,您可能遇到會超過五分鐘的延遲。

發生的原因

SQL Server 企業管理員呼叫 sp_MSdbuseraccess 預存程序,以判斷每個資料庫的存取範圍。 針對每個資料庫在 SQL 中再執行 sp_MSdbuseraccess 預存程序來判斷該資料庫中的使用者 ’s 權限層級的伺服器。將執行的資料庫的 [x 數目 sp_MSdbuseraccess 預存程序 x + 1 的時間。這些多個呼叫可能需要數分鐘才能完成。忙碌的系統可以進行花較長的時間完成呼叫。

解決方案

本文包含 sp_MSdbuseraccess 預存程序的修改的版本。 sp_MSdbuseraccess 預存程序的修改的版本可以解決這個問題,藉由執行 sp_MSdbuseraccess 預存程序那些資料庫中使用者有而不是所有資料庫中執行 sp_MSdbuseraccess 預存程序等權限的位置。多個使用者具有存取權較少的效能,取得使用者的資料庫已達到使用修改過的 sp_MSdbuseraccess 預存程序版本。

下列資訊適用於 sp_MSdbuseraccess 預存程序的修改的版本:
  • 使用者只能看到他們能存取到 SQL Server 企業管理員] 中的資料庫。其中一種下列條件時,使用者會有一個資料庫的存取權:
    • 使用者已授與存取到資料庫使用 sp_grantdbaccess 預存程序。
    • 資料庫包含來賓帳戶。
  • 回應時間的 SQL Server 企業管理員不可能會增加的使用者可以存取許多資料庫。

其他相關資訊

若要使用修改過的 sp_MSdbuseraccess 預存程序程式碼,請依照下列步驟執行:
  1. Copy the following code:
    /*******************************************************************************/
    /* exec sp_MSdbuseraccess 'perm', 'dbname' -- selecting priv bit from specified database                       */
    /* exec sp_MSdbuseraccess 'db', 'dbname'   -- select databases, must change the database if dbname is specified */
    /* exec sp_MSdbuseraccess 'init', 'dbname' -- noop                                                       */
    /*******************************************************************************/
    print N''
    print N'Dropping sp_MSdbuseraccess'
    print N''
    go
    if exists (select * from master.dbo.sysobjects where (OBJECTPROPERTY(id, N'IsProcedure') = 1 or OBJECTPROPERTY(id, N'IsExtendedProc') = 1) and name = N'sp_MSdbuseraccess')
    	drop procedure sp_MSdbuseraccess
    go
    
    print N''
    print N'Creating sp_MSdbuseraccess'
    print N''
    go
    create proc sp_MSdbuseraccess
    	@mode nvarchar(10) = N'perm', @qual nvarchar(128) = N'%'
    as
       set deadlock_priority low
       
       create table #TmpDbUserProfile (
          dbid        int NOT NULL PRIMARY KEY,
          accessperms int NOT NULL
          )
    
       create table #TmpOut (
          name        nvarchar(132) NOT NULL,
          version     smallint,
          crdate      datetime,
          owner       nvarchar(132),
          dbid        smallint NOT NULL,
          status      int,
          category    int,
          status2     int,
          fulltext    int,
          )
    
       set nocount on
    
       declare @accessbit int
    	if (lower(@mode) like N'perm%') begin
          /* verify */
          declare @id int, @stat int, @inval int
          select @id = dbid, @stat = status from master.dbo.sysdatabases where name = @qual
          if (@id is null) begin
             RAISERROR (15001, -1, -1, @qual)
             return 1
          end
    
          /* Can we access this database? */
          declare @single int
          select @single = DATABASEPROPERTY( @qual, N'issingleuser' )
    /*      if ((@single <> 0) or ((@stat & SQLDMODBStat_Inaccessible) <> 0)) begin  */
          if ((@single <> 0) or
             (DATABASEPROPERTY(@qual, N'isdetached') <> 0) or
             (DATABASEPROPERTY(@qual, N'isshutdown') <> 0) or
             (DATABASEPROPERTY(@qual, N'issuspect') <> 0) or
             (DATABASEPROPERTY(@qual, N'isoffline') <> 0) or
             (DATABASEPROPERTY(@qual, N'isinload') <> 0) or
             (DATABASEPROPERTY(@qual, N'isinrecovery') <> 0) or
             (DATABASEPROPERTY(@qual, N'isnotrecovered') <> 0)) begin
             select @inval = 0x80000000
             select @inval
             return 0
          end
          select @accessbit = has_dbaccess(@qual)
          if ( @accessbit <> 1) begin
             select @inval = 0x40000000
             select @inval
             return 0
          end
    
          /** We can access this database, and we must locate the specified database to get the priv bit **/
          declare @dbTempname nvarchar(258)
          declare @tempindex int
          SELECT @dbTempname = REPLACE(@qual, N']', N']]')
          exec (N'[' + @dbTempname + N']' + N'..sp_MSdbuserpriv ')
          return 0
       end
    
       /* If 'db', we want to know what kind of access we have to the specified databases */
       /* If we are not in the master database, we are selecting a single database, and we want to correct role bit to save round trip */
       if (lower(@mode) like N'db%') begin
          /*  Make sure that you are in either the master database or the current database, so that you do not affect other databases. */
          declare @dbrole int
          select @dbrole = 0x0000
    
          if (db_id() <> 1)
             select @qual = db_name()
    
          /* If dbname contains a single quotation mark ('), double the single quotation mark for the cursor because the cursor statement is inside two single quotation marks ('').  */
          declare @qual2 nvarchar(517)
          SELECT @qual2 = REPLACE(@qual, N'''', N'''''')
    
          /* The preprocessor will not replace information within quotation marks, and we have to use the str function. */
          declare @invalidlogin nvarchar(12)
          select @invalidlogin = ltrim(str(convert(int, 0x40000000), 11))
          declare @inaccessible nvarchar(12)
          select @inaccessible = ltrim(str(convert(int, 0x80000000), 11))
    
          /* We cannot 'use' a database that has a version that is less than the minimum version. */
          /* The SQL Server 6.0 version minimum is 406; the SQL Server 6.5 version minimum is 408.  The SQL Server 7.0  version is 408; however,  it might change later. */
          declare @mindbver smallint
          if (@@microsoftversion >= 0x07000000)
             select @mindbver = 408
          else
             select @mindbver = 406
    
          /* Select all matching databases -- we want an entry even for the inaccessible databases. */
          declare @dbid smallint, @dbidstr nvarchar(12), @dbstat int, @dbname nvarchar(258), @dbver smallint
          declare @dbbits int, @dbbitstr nvarchar(12)
    
          /* !!! If the database name contains a left bracket ([), the LIKE operator cannot find the name because the LIKE operator treats a left bracket  as a wildcard character. */
          /* !!! If the @qual2 variable is a percent sign (%), the equal (=) operator does not work. */
          declare @temp int
          select @tempindex = charindex(N'[', @qual2)
          if (@tempindex <> 0)
             exec(N'declare hCdbs cursor global for select name, dbid, status, version from master.dbo.sysdatabases where name = N''' + @qual2 + N'''')
          else
             exec(N'declare hCdbs cursor global for select name, dbid, status, version from master.dbo.sysdatabases where name like N''' + @qual2 + N'''')
    
          open hCdbs
    
          /* Loop for each database; if the database can be accessed, recursively call ourselves to add the database. */
          fetch hCdbs into @dbname, @dbid, @dbstat, @dbver
          while (@@fetch_status >= 0) begin
             /* The preprocessor will not replace information within quotation marks, and we have to use the str function. */
             select @dbidstr = ltrim(str(convert(int, @dbid)))
    
             /* If the database is a single user database and there is an entry for it in sysprocesses that is not us, we cannot use it. */
             declare @single_lockedout int
             select @single_lockedout = DATABASEPROPERTY( @dbname, N'issingleuser' )
             if (@single_lockedout <> 0)
                select @single_lockedout = 0 where not exists
                   (select * from master.dbo.sysprocesses p where dbid = @dbid and p.spid <> @@spid)
    
             /* First, see if the database can be accessed (not in load, not in recovery, not offline, not in single-use with another user besides us, and so on.) */
    /*         if ((@single_lockedout <> 0) or ((@dbstat & SQLDMODBStat_Inaccessible) <> 0) or (@dbver < @mindbver)) begin   */
             if ((@single_lockedout <> 0) or
                (@dbver < @mindbver) or
                (DATABASEPROPERTY(@dbname, N'isdetached') <> 0) or
                (DATABASEPROPERTY(@dbname, N'isshutdown') <> 0) or
                (DATABASEPROPERTY(@dbname, N'issuspect') <> 0) or
                (DATABASEPROPERTY(@dbname, N'isoffline') <> 0) or
                (DATABASEPROPERTY(@dbname, N'isinload') <> 0) or
                (DATABASEPROPERTY(@dbname, N'isinrecovery') <> 0) or
                (DATABASEPROPERTY(@dbname, N'isnotrecovered') <> 0) ) begin
                /* Inaccessible, but we can set dbo if we are an sa or if the suser_id function is the database owner sid. */
                exec (N'insert #TmpDbUserProfile values (' + @dbidstr + N', ' + @inaccessible + N')')
                end
             else begin
                /* Determine whether the current user has access to the database. */
                select @accessbit = has_dbaccess(@dbname)
                if ( @accessbit <> 1) begin
                   exec (N'insert #TmpDbUserProfile values (' + @dbidstr + N', ' + @invalidlogin + N')')
                   end
                else begin
                   /* The current user does have access to this database, and we are not trying to obtain priv at this point. */
                   select @dbbits = 0x03ff
                   select @dbbitstr = ltrim(convert(nvarchar(12), @dbbits))
                   exec (N'insert #TmpDbUserProfile values (' + @dbidstr + N', ' + @dbbitstr + N')')
                   end
                end
    
             fetch hCdbs into @dbname, @dbid, @dbstat, @dbver
          end /* while FETCH_SUCCESS */
          close hCdbs
          deallocate hCdbs
    
          /* Select sysdatabases information in the temp table first to avoid a deadlock in the restore process. */
          if (@tempindex <> 0)
             insert #TmpOut
             select o.name, o.version, o.crdate, suser_sname(o.sid), o.dbid, o.status, o.category, o.status2, DatabaseProperty(o.name, N'isfulltextenabled')
                from master.dbo.sysdatabases o where o.name = @qual
          else
             insert #TmpOut
             select o.name, o.version, o.crdate, suser_sname(o.sid), o.dbid, o.status, o.category, o.status2, DatabaseProperty(o.name, N'isfulltextenabled')
                from master.dbo.sysdatabases o where o.name like @qual
    
          /* 1. If on all databases,  dbrole is dummy, we must obtain it later. */
          /* 2. Do not double the single quotation mark (') characters in the database name. */
          /* 3. To speed up the connection, the accessperms column only indicates whether the user can access the database. The column does not contain */
          /*    permission information. We will retrieve the permission information by using the sp_MSdbuserpriv stored procedure when we need that information. */
          /* !!! If the name contains a left bracket ('[), the LIKE operator cannot find it because the LIKE operator treats a left bracket  as a wildcard character. */
          /* !!! If the @qual2 variable is a percent sign (%), the equal (=) operator does not work. */
          if (@tempindex <> 0)
             select o.name, o.version, o.crdate, o.owner, o.dbid, lSize = 0, NonDbo = 0, Status = o.status, spaceavail = 0,
                LogOnSepDev = 1, o.category, t.accessperms, @dbrole, o.fulltext, o.status2,
                collation = convert(sysname, databasepropertyex(o.name, N'collation'))
                from #TmpOut o left outer join #TmpDbUserProfile t on t.dbid = o.dbid 
    	where o.name = @qual and (t.accessperms & 0x40000000 = 0) order by o.name
          else
             select o.name, o.version, o.crdate, o.owner, o.dbid, lSize = 0, NonDbo = 0, Status = o.status, spaceavail = 0,
                LogOnSepDev = 1, o.category, t.accessperms, @dbrole, o.fulltext, o.status2,
                collation = convert(sysname, databasepropertyex(o.name, N'collation'))
                from #TmpOut o left outer join #TmpDbUserProfile t on t.dbid = o.dbid 
    		where o.name like @qual and (t.accessperms & 0x40000000 = 0) order by o.name
    
          DROP TABLE #TmpDbUserProfile
          DROP TABLE #TmpOut
          return 0
       end
    go
    /* End sp_MSdbuseraccess */
    
    exec sp_MS_marksystemobject sp_MSdbuseraccess
    go
    grant execute on sp_MSdbuseraccess to public
    go
  2. 開啟 SQL 查詢分析器,然後將程式碼貼到空白的查詢分析器視窗。
  3. 使用 [EXECUTE 」 指令,以針對 SQL Server 資料庫執行修改的 sp_MSdbuseraccess 預存程序。
附註這個程式碼會將修改過的 sp_MSdbuseraccess 預存程序與系統物件標示藉由使用 sp_MS_marksystemobject 預存程序。

如果您實作此程式碼,然後決定要回復預設行為時,請依照下列步驟執行:
  1. 找出原始的建立程序指令碼的 sp_MSdbusreaccess 預存程序。

    附註指令碼通常位於 [Sqldmo.sql 程式 Files\Microsoft SQL Server\MSSQL\Install 資料夾中的檔案。
  2. 開啟 [Sqldmo.sql SQL 查詢分析器中的檔案。
  3. 在指令碼中找出下列批次,然後再執行 [卸除目前版本 sp_MSdbuseraccess 預存程序的批次:
    if exists
    				(select * from master.dbo.sysobjects where (OBJECTPROPERTY(id, N'IsProcedure')
    				= 1 or OBJECTPROPERTY(id, N'IsExtendedProc') = 1) and name =
    				N'sp_MSdbuseraccess') drop procedure sp_MSdbuseraccess go
  4. 搜尋字串的開頭建立程序 sp_MSdbuseraccess。
  5. 執行批次以開頭的"建立程序 sp_MSdbuseraccess ’ 」 中重新建立原始程序的 SQL 查詢分析器。

這篇文章中的資訊適用於:
  • Microsoft SQL Server 2000 Personal Edition
  • Microsoft SQL Server 2000 Standard Edition
  • Microsoft SQL Server 2000 Developer Edition
  • Microsoft SQL Server 2000 Enterprise Edition
關鍵字:?
kbmt kbprb kbtshoot KB889696 KbMtzh
機器翻譯機器翻譯
重要:本文是以 Microsoft 機器翻譯軟體翻譯而成,而非使用人工翻譯而成。Microsoft 同時提供使用者人工翻譯及機器翻譯兩個版本的文章,讓使用者可以依其使用語言使用知識庫中的所有文章。但是,機器翻譯的文章可能不盡完美。這些文章中也可能出現拼字、語意或文法上的錯誤,就像外國人在使用本國語言時可能發生的錯誤。Microsoft 不為內容的翻譯錯誤或客戶對該內容的使用所產生的任何錯誤或損害負責。Microsoft也同時將不斷地就機器翻譯軟體進行更新。
按一下這裡查看此文章的英文版本:889696? (http://support.microsoft.com/kb/889696/en-us/ )
Microsoft及(或)其供應商不就任何在本伺服器上發表的文字資料及其相關圖表資訊的恰當性作任何承諾。所有文字資料及其相關圖表均以「現狀」供應,不負任何擔保責任。Microsoft及(或)其供應商謹此聲明,不負任何對與此資訊有關之擔保責任,包括關於適售性、適用於某一特定用途、權利或不侵權的明示或默示擔保責任。Microsoft及(或)其供應商無論如何不對因或與使用本伺服器上資訊或與資訊的實行有關而引起的契約、過失或其他侵權行為之訴訟中的特別的、間接的、衍生性的損害或任何因使用而喪失所導致的之損害、資料或利潤負任何責任。