月度归档:2014年11月

unicode,utf8,utf16

什么是编码

在阅读本文之前建议读者先去阅读这篇文章:http://www.freebuf.com/articles/others-articles/25623.html ,如果你没有耐心读完他也没关系,只需要明白三个道理:

1,这个世界上从来没有纯文本这回事,如果你想读出一个字符串,你必须知道它的编码。如果你不知道一段数据流的编码方式,你就永远不会知道这里面的内容。

2,Unicode是一个简单的标准,用来把字符映射到数字上。Unicode协会的人会帮你处理所有幕后的问题,包括为新字符指定编码。我们用的所有字符都在unicode里面有对应的映射,每个映射称为一个码点( http://en.wikipedia.org/wiki/Code_point 

3,Unicode并不告诉你字符是怎么编码成字节的。这是被编码方案决定的,通过UTF来指定。

读完前面这篇文章之后你也许就了解了一个二进制流到屏幕字符的过程:

二进制流->根据编码方式解码出码点->根据unicode码点解释出字符->系统渲染绘出这个字符

文本字符保存到计算机上的过程:

输入字符->根据字符找到对应码点->根据编码方式把码点编码成二进制流->保存二进制流到硬盘上

从这个过程我们可以知道能不能从二进制流读取出字符关键就在于能不能找到二进制流的编码,掌握了编码方式的信息就可以用对应的逆过程解码。

看到这里有读者一定会问:为什么要编码,根据二进制流计算码点不好吗?

原因是良好设计的编码可以为我们提供很多附加的功能,包括容错纠错(在网络通信中尤其重要),自同步(不必从文本头部开始就可以解码)等等。编码从信息论的角度上来说就是增加了冗余的信息,冗余的这部分信息就可以为我们提供额外的功能。

utf8的编码规则

我们来看utf8和utf16具体是如何编码的:

Utf8有如下特点:

1.可变长编码,由第一个字节决定该字符编码长度

2.向下兼容ascii码(这也是为什么用utf8编码可以完美打开ascii文本文件)

Utf8的编码规则:

  1. 一个字节的编码完全用于ascii码(从0-127)
  2. 大于127的码点都用多字节来编码,多字节包含开头字节和后续字节

开头字节以若干个1开头(长度为几就有几个1,因此只要读完开头字节就可以知道本字符共有多少个字节),后接1个0.后续字节都以10开头

  1. 从右到做,后续字节每个字节占用原码点6个位,剩余的放在开头字节。
  2. 开头字节和后续字节不共享任何数据,因此utf8是自同步的。举例来说我们看到一个字节以110…开头时,我们就知道这是一个2字节的字符的开头字节。

具体来举几个例子:

具体来举几个例子:

字符 码点 二进制 UTF-8 16进制  UTF-8
$ U+0024 0100100 00100100 24
¢ U+00A2 000 10100010 11000010 10100010 C2 A2
U+20AC 00100000 10101100 11100010 10000010 10101100 E2 82 AC
�� U+24B62

00010

01001011 01100010

11110000 10100100 

10101101 10100010

F0 A4 AD A2

 

 

 

实现了UTF8编码的java代码:

public class Utf8 {     /**      * @param codePoint in unicode
     * @return corresponding utf8 bytes
     * @throws Exception
     */     private static final long RightSix = (1 << 6) - 1;
    private static final long PrefixForContinuasByte = 1 << 7;

    public static long EncodeToUtf8(long codePoint) throws Exception
    {
        if (codePoint < 0 || codePoint > 0x1FFFFF)
            throw new Exception("Illegal code point!");
        if (codePoint <= 0x007F)
        {
            return codePoint;// ascii character         }
        else if (codePoint <= 0x07FF)
        {
            long byte1 = (6 << 5) + (codePoint >> 6);
            long byte2 = PrefixForContinuasByte + (codePoint & RightSix);
            return (byte1 << 8) + byte2;
        }
        else if (codePoint <= 0xFFFF)
        {
            long byte1 = (14 << 4) + (codePoint >> 12);
            long byte2 = PrefixForContinuasByte + ((codePoint >> 6) & RightSix);
            long byte3 = PrefixForContinuasByte + (codePoint & RightSix);
            return (byte1 << 16) + (byte2 << 8) + byte3;
        }
        else         {
            long byte1 = (30 << 3) + (codePoint >> 18);
            long byte2 = PrefixForContinuasByte
                    + ((codePoint >> 12) & RightSix);
            long byte3 = PrefixForContinuasByte + ((codePoint >> 6) & RightSix);
            long byte4 = PrefixForContinuasByte + (codePoint & RightSix);
            return (byte1 << 24) + (byte2 << 16) + (byte3 << 8) + byte4;
        }
    }public static void main(String[] args)
    {
        try         {
            while (true)
            {
                System.out.print("Input a number in Hex format:");
                Scanner sc = new Scanner(System.in);
                String s = sc.nextLine();
                // System.out.println("it is "+HexStringToLong(s)+" in decimal format");                 long utf8 = EncodeToUtf8(HexStringToLong(s));
                String hexString = Long.toHexString(utf8);
                System.out.println("You input " + s
                        + " in Hex format and we encode it to utf8 character "                         + hexString);
            }
        }
        catch (Exception e)
        {
            System.out.println(e.getLocalizedMessage());
            // TODO: handle exception         }

    }
}

运行结果:

Input a number in Hex format:24 You input 24 in Hex format and we encode it to utf8 character 24 Input a number in Hex format:A2 You input A2 in Hex format and we encode it to utf8 character c2a2Input a number in Hex format:20ACYou input 20AC in Hex format and we encode it to utf8 character e282acInput a number in Hex format:24B62You input 24B62 in Hex format and we encode it to utf8 character f0a4ada2

关于UTF-16的编码规则,读者可以参考这篇文章:http://en.wikipedia.org/wiki/UTF-16

这里附上UTF16-BE的编码代码:

public class Utf16 {     /**      * @param codePoint in unicode
     * @return corresponding utf16 bytes
     * @throws Numberformat Exception
     */     
    private static final long Substracted=0x10000; 
    private static final long AddToHigh=0xD800;
    private static final long AddToLow=0xDC00;
    private static long HexStringToLong(String s)
    {
        if (s.length() == 0)
            return 0;
        long ans = 0;
        for (int i = 0; i < s.length(); i++)
        {
            char c = s.charAt(i);
            if (c >= '0' && c <= '9')
                ans = (ans << 4) + (c - '0');
            else if (c >= 'A' && c <= 'F')
                ans = (ans << 4) + (c - 'A' + 10);
            else                 throw new NumberFormatException();
        }
        return ans;
    }
    public static long EncodeToUtf16BE(long codePoint) throws Exception
    {
        if(codePoint<0||(codePoint<=0xDFFF&&codePoint>=0xD800)||codePoint>0x10FFFF)
            throw new NumberFormatException();
        if(codePoint<=0xD7FF)//Basic Multilingual Plane         {
            return codePoint;
        }
        else         {
            long sub=codePoint-Substracted;
            long high=sub>>10;
            long low=sub&0x3FF;
            long word1=AddToHigh+high;
            long word2=AddToLow+low;
            return (word1<<16)+word2;
        }    
    }
    public static void main(String[] args)
    {
        while(true)
        {
            System.out.print("Input a number in hex format");
            Scanner sc=new Scanner(System.in);            
            String s=sc.nextLine();
            try             {
                String utf16=Long.toHexString(EncodeToUtf16BE(HexStringToLong(s)));
                System.out.println("You input "+s+" we encode it to utf16-BE "+utf16);
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
            
            
        }
    }
}

SQL Server死锁

其实所有的死锁最深层的原因就是一个:资源竞争

 

表现一:

  一个用户A 访问表A(锁住了表A),然后又访问表B,另一个用户B 访问表B(锁住了表B),然后企图访问表A,这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B,才能继续,好了他老人家就只好老老实实在这等了,同样用户B要等用户A释放表A才能继续这就死锁了。

  解决方法:

  这种死锁是由于你的程序的BUG产生的,除了调整你的程序的逻辑别无他法

  仔细分析你程序的逻辑:

  1:尽量避免同时锁定两个资源

  2: 必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源.

 

表现二:

  用户A读一条纪录,然后修改该条纪录。这是用户B修改该条纪录,这里用户A的事务里锁的性质由共享锁企图上升到独占锁(for update),而用户B里的独占锁由于A有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁。

  这种死锁比较隐蔽,但其实在稍大点的项目中经常发生。

  解决方法:

  让用户A的事务(即先读后写类型的操作),在select 时就是用Update lock

  语法如下:

        select * from table1 with(updlock) where ....

 

--------------------------------------------------------------------------------

接上面文章,继续探讨数据库死锁问题 

 

死锁,简而言之,两个或者多个trans,同时请求对方正在请求的某个对象,导致双方互相等待。简单的例子如下:
   trans1                                                    trans2
   ------------------------------------------------------------------------------------------------
   1.IDBConnection.BeginTransaction    1.IDBConnection.BeginTransaction
   2.update table A                                2.update table B
   3.update table B                                3.update table A
   4.IDBConnection.Commit                   4.IDBConnection.Commit 
   那么,很容易看到,如果trans1和trans2,分别到达了step3,那么trans1会请求对于B的X锁,trans2会请求对于A的X锁,而二者的锁在step2上已经被对方分别持有了。由于得不到锁,后面的Commit无法执行,这样双方开始死锁。

   好,我们看一个简单的例子,来解释一下,应该如何解决死锁问题。

   -- Batch #1

   CREATE DATABASE deadlocktest
   GO

   USE deadlocktest
   SET NOCOUNT ON
   DBCC TRACEON (1222, -1)
   -- 在SQL2005中,增加了一个新的dbcc参数,就是1222,原来在2000下,我们知道,可以执行dbcc    
   -- traceon(1204,3605,-1)看到所有的死锁信息。SqlServer 2005中,对于1204进行了增强,这就是1222。
   GO   
   
   IF OBJECT_ID ('t1') IS NOT NULL DROP TABLE t1
   IF OBJECT_ID ('p1') IS NOT NULL DROP PROC p1
   IF OBJECT_ID ('p2') IS NOT NULL DROP PROC p2
   GO

 

   CREATE TABLE t1 (c1 int, c2 int, c3 int, c4 char(5000))
   GO

 

   DECLARE @x int
   SET @x = 1
   WHILE (@x <= 1000) BEGIN
            INSERT INTO t1 VALUES (@x*2, @x*2, @x*2, @x*2)
            SET @x = @x + 1
   END
   GO

 

   CREATE CLUSTERED INDEX cidx ON t1 (c1)
   CREATE NONCLUSTERED INDEX idx1 ON t1 (c2)
   GO

 

   CREATE PROC p1 @p1 int AS SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
   GO

 

   CREATE PROC p2 @p1 int AS
            UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
            UPDATE t1 SET c2 = c2-1 WHERE c1 = @p1
   GO

 

   上述sql创建一个deadlock的示范数据库,插入了1000条数据,并在表t1上建立了c1列的聚集索引,和c2列的非聚集索引。另外创建了两个sp,分别是从t1中select数据和update数据。

   好,打开一个新的查询窗口,我们开始执行下面的query:

   -- Batch #2

 

   USE deadlocktest
   SET NOCOUNT ON
   WHILE (1=1) EXEC p2 4
   GO

 

   开始执行后,然后我们打开第三个查询窗口,执行下面的query:

   -- Batch #3

 

   USE deadlocktest
   SET NOCOUNT ON
   CREATE TABLE #t1 (c2 int, c3 int)
   GO

 

   WHILE (1=1) BEGIN
             INSERT INTO #t1 EXEC p1 4
             TRUNCATE TABLE #t1
   END
   GO

 

   开始执行,哈哈,很快,我们看到了这样的错误信息:
   Msg 1205, Level 13, State 51, Procedure p1, Line 4
   Transaction (Process ID 54) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

 

   spid54发现了死锁。
   那么,我们该如何解决它?

   在SqlServer 2005中,我们可以这么做:
   1.在trans3的窗口中,选择EXEC p1 4,然后right click,看到了菜单了吗?选择Analyse Query in Database Engine Tuning Advisor。
   2.注意右面的窗口中,wordload有三个选择:负载文件、表、查询语句,因为我们选择了查询语句的方式,所以就不需要修改这个radio option了。
   3.点左上角的Start Analysis按钮
   4.抽根烟,回来后看结果吧!出现了一个分析结果窗口,其中,在Index Recommendations中,我们发现了一条信息:大意是,在表t1上增加一个非聚集索引索引:t2+t1。
   5.在当前窗口的上方菜单上,选择Action菜单,选择Apply Recommendations,系统会自动创建这个索引。

   重新运行batch #3,呵呵,死锁没有了。

为什么会死锁呢?再回顾一下两个sp的写法:
   CREATE PROC p1 @p1 int AS 
      SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
   GO

   CREATE PROC p2 @p1 int AS
         UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
         UPDATE t1 SET c2 = c2-1 WHERE c1 = @p1
   GO

 

   很奇怪吧!p1没有insert,没有delete,没有update,只是一个select,p2才是update。这个和我们前面说过的,trans1里面updata A,update B;trans2里面upate B,update A,根本不贴边啊!
   那么,什么导致了死锁?

   需要从事件日志中,看sql的死锁信息:
   Spid X is running this query (line 2 of proc [p1], inputbuffer “… EXEC p1 4 …”): 
   SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
   Spid Y is running this query (line 2 of proc [p2], inputbuffer “EXEC p2 4”): 
   UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
                
   The SELECT is waiting for a Shared KEY lock on index t1.cidx.  The UPDATE holds a conflicting X lock. 
   The UPDATE is waiting for an eXclusive KEY lock on index t1.idx1.  The SELECT holds a conflicting S lock.

 

   首先,我们看看p1的执行计划。怎么看呢?可以执行set statistics profile on,这句就可以了。下面是p1的执行计划

   SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
        |--Nested Loops(Inner Join, OUTER REFERENCES:([Uniq1002], [t1].[c1]))
               |--Index Seek(OBJECT:([t1].[idx1]), SEEK:([t1].[c2] >= [@p1] AND [t1].[c2] <= [@p1]+(1)) ORDERED FORWARD)
                     |--Clustered Index Seek(OBJECT:([t1].[cidx]), SEEK:([t1].[c1]=[t1].[c1] AND [Uniq1002]=[Uniq1002]) LOOKUP ORDERED FORWARD)

 

   我们看到了一个nested loops,第一行,利用索引t1.c2来进行seek,seek出来的那个rowid,在第二行中,用来通过聚集索引来查找整行的数据。这是什么?就是bookmark lookup啊!为什么?因为我们需要的c2、c3不能完全的被索引t1.c1带出来,所以需要书签查找。
   好,我们接着看p2的执行计划。

   UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
         |--Clustered Index Update(OBJECT:([t1].[cidx]), OBJECT:([t1].[idx1]), SET:([t1].[c2] = [Expr1004]))
               |--Compute Scalar(DEFINE:([Expr1013]=[Expr1013]))
                     |--Compute Scalar(DEFINE:([Expr1004]=[t1].[c2]+(1), [Expr1013]=CASE WHEN CASE WHEN ...
                           |--Top(ROWCOUNT est 0)
                                 |--Clustered Index Seek(OBJECT:([t1].[cidx]), SEEK:([t1].[c1]=[@p1]) ORDERED FORWARD)

 

   通过聚集索引的seek找到了一行,然后开始更新。这里注意的是,update的时候,它会申请一个针对clustered index的X锁的。

   实际上到这里,我们就明白了为什么update会对select产生死锁。update的时候,会申请一个针对clustered index的X锁,这样就阻塞住了(注意,不是死锁!)select里面最后的那个clustered index seek。死锁的另一半在哪里呢?注意我们的select语句,c2存在于索引idx1中,c1是一个聚集索引cidx。问题就在这里!我们在p2中更新了c2这个值,所以sqlserver会自动更新包含c2列的非聚集索引:idx1。而idx1在哪里?就在我们刚才的select语句中。而对这个索引列的更改,意味着索引集合的某个行或者某些行,需要重新排列,而重新排列,需要一个X锁。
   SO………,问题就这样被发现了。

 

   总结一下,就是说,某个query使用非聚集索引来select数据,那么它会在非聚集索引上持有一个S锁。当有一些select的列不在该索引上,它需要根据rowid找到对应的聚集索引的那行,然后找到其他数据。而此时,第二个的查询中,update正在聚集索引上忙乎:定位、加锁、修改等。但因为正在修改的某个列,是另外一个非聚集索引的某个列,所以此时,它需要同时更改那个非聚集索引的信息,这就需要在那个非聚集索引上,加第二个X锁。select开始等待update的X锁,update开始等待select的S锁,死锁,就这样发生鸟。

 

   那么,为什么我们增加了一个非聚集索引,死锁就消失鸟?我们看一下,按照上文中自动增加的索引之后的执行计划:

   SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
      |--Index Seek(OBJECT:([deadlocktest].[dbo].[t1].[_dta_index_t1_7_2073058421__K2_K1_3]), SEEK:([deadlocktest].[dbo].[t1].[c2] >= [@p1] AND [deadlocktest].[dbo].[t1].[c2] <= [@p1]+(1)) ORDERED FORWARD)

 

   哦,对于clustered index的需求没有了,因为增加的覆盖索引已经足够把所有的信息都select出来。就这么简单。

 

   实际上,在sqlserver 2005中,如果用profiler来抓eventid:1222,那么会出现一个死锁的图,很直观的说。

 

   下面的方法,有助于将死锁减至最少(详细情况,请看SQLServer联机帮助,搜索:将死锁减至最少即可。

       . 按同一顺序访问对象。 
       . 避免事务中的用户交互。 
       . 保持事务简短并处于一个批处理中。 
       . 使用较低的隔离级别。 
       . 使用基于行版本控制的隔离级别。 
              . 将 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON,使得已提交读事务使用行版本控制。 
              . 使用快照隔离。 
       . 使用绑定连接。

SQL Server 索引结构

作者:freedk 
一、深入浅出理解索引结构
  实际上,您可以把索引理解为一种特殊的目录。微软的SQL SERVER提供了两种索引:聚集索引(clustered index,也称聚类索引、簇集索引)和非聚集索引(nonclustered index,也称非聚类索引、非簇集索引)。下面,我们举例来说明一下聚集索引和非聚集索引的区别: 
  其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查“张”字,那您也会将您的字典翻到最后部分,因为“张”的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。 
  如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。 
  通过以上例子,我们可以理解到什么是“聚集索引”和“非聚集索引”。进一步引申一下,我们可以很容易的理解:每个表只能有一个聚集索引,因为目录只能按照一种方法进行排序。
 
二、何时使用聚集索引或非聚集索引
下面的表总结了何时使用聚集索引或非聚集索引(很重要):
动作描述 使用聚集索引 使用非聚集索引
列经常被分组排序
返回某范围内的数据 不应
一个或极少不同值 不应 不应
小数目的不同值 不应
大数目的不同值 不应
频繁更新的列 不应
外键列
主键列
频繁修改索引列 不应
  事实上,我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。如:返回某范围内的数据一项。比如您的某个表有一个时间列,恰好您把聚合索引建立在了该列,这时您查询2004年1月1日至2004年10月1日之间的全部数据时,这个速度就将是很快的,因为您的这本字典正文是按日期进行排序的,聚类索引只需要找到要检索的所有数据中的开头和结尾数据即可;而不像非聚集索引,必须先查到目录中查到每一项数据对应的页码,然后再根据页码查到具体内容。

SQL Server死锁总结

当前锁查看:

use master
go

--检索死锁进程
select spid, blocked, loginame, last_batch, status, cmd, hostname, program_name
from sysprocesses
where spid in
( select blocked from sysprocesses where blocked <> 0 ) or (blocked <>0)

--或者:

select request_session_id,OBJECT_NAME(resource_associated_entity_id) tableName from
sys.dm_tran_locks
where resource_type='OBJECT'

--

kill   进程号(sp_lock/sp_who查找死锁的进程和对象) 

spid就是锁住表的进程

tableName就是被锁的表名

解锁

kill [spid]

 

 1. 死锁原理

    根据操作系统中的定义:死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。

    死锁的四个必要条件:
互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。
非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。

对应到SQL Server中,当在两个或多个任务中,如果每个任务锁定了其他任务试图锁定的资源,此时会造成这些任务永久阻塞,从而出现死锁;这些资源可能是:单行(RID,堆中的单行)、索引中的键(KEY,行锁)、页(PAG8KB)、区结构(EXT,连续的8)、堆或B(HOBT) 、表(TAB,包括数据和索引)、文件(File,数据库文件)、应用程序专用资源(APP)、元数据(METADATA)、分配单元(Allocation_Unit)、整个数据库(DB)一个死锁示例如下图所示:

点击查看原图
    说明:
T1T2表示两个任务;R1R2表示两个资源;由资源指向任务的箭头(R1->T1R2->T2)表示该资源被改任务所持有;由任务指向资源的箭头(T1->S2T2->S1)表示该任务正在请求对应目标资源;
    其满足上面死锁的四个必要条件:
(1).互斥:资源S1S2不能被共享,同一时间只能由一个任务使用;
(2).请求与保持条件:T1持有S1的同时,请求S2T2持有S2的同时请求S1
(3).非剥夺条件:T1无法从T2上剥夺S2T2也无法从T1上剥夺S1
(4).循环等待条件:上图中的箭头构成环路,存在循环等待。

 

2. 死锁排查

(1). 使用SQL Server的系统存储过程sp_whosp_lock,可以查看当前数据库中的锁情况;进而根据objectID(@objID)(SQL Server 2005)/ object_name(@objID)(Sql Server 2000)可以查看哪个资源被锁,用dbcc ld(@blk),可以查看最后一条发生给SQL ServerSql语句;

CREATE Table #Who(spid int,

    ecid int,

    status nvarchar(50),

    loginname nvarchar(50),

    hostname nvarchar(50),

    blk int,

    dbname nvarchar(50),

    cmd nvarchar(50),

    request_ID int);



CREATE Table #Lock(spid int,

    dpid int,

    objid int,

    indld int,

    [Type] nvarchar(20),

    Resource nvarchar(50),

    Mode nvarchar(10),

    Status nvarchar(10)

);



INSERT INTO #Who

    EXEC sp_who active  --看哪个引起的阻塞,blk 

INSERT INTO #Lock

    EXEC sp_lock  --看锁住了那个资源id,objid 



DECLARE @DBName nvarchar(20);

SET @DBName='NameOfDataBase'



SELECT #Who.* FROM #Who WHERE dbname=@DBName

SELECT #Lock.* FROM #Lock

    JOIN #Who

        ON #Who.spid=#Lock.spid

            AND dbname=@DBName;



--最后发送到SQL Server的语句

DECLARE crsr Cursor FOR

    SELECT blk FROM #Who WHERE dbname=@DBName AND blk<>0;

DECLARE @blk int;

open crsr;

FETCH NEXT FROM crsr INTO @blk;

WHILE (@@FETCH_STATUS = 0)

BEGIN;

    dbcc inputbuffer(@blk);

    FETCH NEXT FROM crsr INTO @blk;

END;

close crsr;

DEALLOCATE crsr;



--锁定的资源

SELECT #Who.spid,hostname,objid,[type],mode,object_name(objid) as objName FROM #Lock

    JOIN #Who

        ON #Who.spid=#Lock.spid

            AND dbname=@DBName

    WHERE objid<>0;



DROP Table #Who;

DROP Table #Lock;

(2). 使用 SQL Server Profiler 分析死锁:  Deadlock graph 事件类添加到跟踪。此事件类使用死锁涉及到的进程和对象的 XML 数据填充跟踪中的 TextData 数据列。SQL Server 事件探查器 可以将 XML 文档提取到死锁 XML (.xdl) 文件中,以后可在 SQL Server Management Studio 中查看该文件。

 

3. 避免死锁

    上面1中列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件,就可以避免死锁发生,一般有以下几种方法(FROM Sql Server 2005联机丛书)
(1).按同一顺序访问对象。(注:避免出现循环)
(2).避免事务中的用户交互。(注:减少持有资源的时间,较少锁竞争)
(3).保持事务简短并处于一个批处理中。(注:同(2),减少持有资源的时间)
(4).使用较低的隔离级别。(注:使用较低的隔离级别(例如已提交读)比使用较高的隔离级别(例如可序列化)持有共享锁的时间更短,减少锁竞争)
(5).使用基于行版本控制的隔离级别2005中支持快照事务隔离和指定READ_COMMITTED隔离级别的事务使用行版本控制,可以将读与写操作之间发生的死锁几率降至最低:
SET ALLOW_SNAPSHOT_ISOLATION ON --事务可以指定 SNAPSHOT 事务隔离级别;
SET READ_COMMITTED_SNAPSHOT ON  --指定 READ_COMMITTED 隔离级别的事务将使用行版本控制而不是锁定。默认情况下(没有开启此选项,没有加with nolock提示)SELECT语句会对请求的资源加S(共享锁);而开启了此选项后,SELECT不会对请求的资源加S锁。
注意:设置 READ_COMMITTED_SNAPSHOT 选项时,数据库中只允许存在执行 ALTER DATABASE 命令的连接。在 ALTER DATABASE 完成之前,数据库中决不能有其他打开的连接。数据库不必一定要处于单用户模式中。
(6).使用绑定连接(注:绑定会话有利于在同一台服务器上的多个会话之间协调操作。绑定会话允许一个或多个会话共享相同的事务和锁(但每个回话保留其自己的事务隔离级别),并可以使用同一数据,而不会有锁冲突。可以从同一个应用程序内的多个会话中创建绑定会话,也可以从包含不同会话的多个应用程序中创建绑定会话。在一个会话中开启事务(begin tran)后,调用exec sp_getbindtoken @Token out;来取得Token,然后传入另一个会话并执行EXEC sp_bindsession @Token来进行绑定(最后的示例中演示了绑定连接)

 

4. 死锁处理方法:

(1). 根据2中提供的sql,查看那个spid处于wait状态,然后用kill spid来干掉(即破坏死锁的第四个必要条件:循环等待);当然这只是一种临时解决方案,我们总不能在遇到死锁就在用户的生产环境上排查死锁、Kill sp,我们应该考虑如何去避免死锁。

(2). 使用SET LOCK_TIMEOUT timeout_period(单位为毫秒)设定锁请求超时。默认情况下,数据库没有超时期限(timeout_period值为-1,可以用SELECT @@LOCK_TIMEOUT来查看该值,即无限期等待)。当请求锁超过timeout_period时,将返回错误。timeout_period值为0时表示根本不等待,一遇到锁就返回消息。设置锁请求超时,破环了死锁的第二个必要条件(请求与保持条件)

服务器: 消息 1222,级别 16,状态 50,行 1
已超过了锁请求超时时段。

(3). SQL Server内部有一个锁监视器线程执行死锁检查,锁监视器对特定线程启动死锁搜索时,会标识线程正在等待的资源;然后查找特定资源的所有者,并递归地继续执行对那些线程的死锁搜索,直到找到一个构成死锁条件的循环。检测到死锁后,数据库引擎 选择运行回滚开销最小的事务的会话作为死锁牺牲品,返回1205 错误,回滚死锁牺牲品的事务并释放该事务持有的所有锁,使其他线程的事务可以请求资源并继续运行。

 

5. 两个死锁示例及解决方法

5.1 SQL死锁

(1). 测试用的基础数据:

CREATE TABLE Lock1(C1 int default(0));
CREATE TABLE Lock2(C1 int default(0));
INSERT INTO Lock1 VALUES(1);
INSERT INTO Lock2 VALUES(1);

(2). 开两个查询窗口,分别执行下面两段sql

--Query 1
Begin Tran
  
Update Lock1 Set C1=C1+1;
  
WaitFor Delay '00:01:00';
  
SELECT * FROM Lock2
Rollback Tran;

 

--Query 2
Begin Tran
  
Update Lock2 Set C1=C1+1;
  
WaitFor Delay '00:01:00';
  
SELECT * FROM Lock1
Rollback Tran;

 

上面的SQL中有一句WaitFor Delay '00:01:00',用于等待1分钟,以方便查看锁的情况。

(3). 查看锁情况

在执行上面的WaitFor语句期间,执行第二节中提供的语句来查看锁信息:

点击查看原图Query1中,持有Lock1中第一行(表中只有一行数据)的行排他锁(RID:X),并持有该行所在页的意向更新锁(PAG:IX)、该表的意向更新锁(TAB:IX)Query2中,持有Lock2中第一行(表中只有一行数据)的行排他锁(RID:X),并持有该行所在页的意向更新锁(PAG:IX)、该表的意向更新锁(TAB:IX)

执行完WaitforQuery1查询Lock2,请求在资源上加S锁,但该行已经被Query2加上了X锁;Query2查询Lock1,请求在资源上加S锁,但该行已经被Query1加上了X锁;于是两个查询持有资源并互不相让,构成死锁。

(4). 解决办法

a). SQL Server自动选择一条SQL作死锁牺牲品:运行完上面的两个查询后,我们会发现有一条SQL能正常执行完毕,而另一个SQL则报如下错误:

服务器: 消息 1205,级别 13,状态 50,行 1
事务(进程 ID  xx)与另一个进程已被死锁在  lock 资源上,且该事务已被选作死锁牺牲品。请重新运行该事务。

这就是上面第四节中介绍的锁监视器干活了。

b). 按同一顺序访问对象:颠倒任意一条SQL中的UpdateSELECT语句的顺序。例如修改第二条SQL成如下:

--Query2
Begin Tran
  
SELECT * FROM Lock1--在Lock1上申请S锁
  WaitFor Delay '00:01:00';
  
Update Lock2 Set C1=C1+1;--Lock2:RID:X
Rollback Tran;

当然这样修改也是有代价的,这会导致第一条SQL执行完毕之前,第二条SQL一直处于阻塞状态。单独执行Query1Query2需要约1分钟,但如果开始执行Query1时,马上同时执行Query2,则Query2需要2分钟才能执行完;这种按顺序请求资源从一定程度上降低了并发性。

c). SELECT语句加With(NoLock)提示:默认情况下SELECT语句会对查询到的资源加S(共享锁)S锁与X(排他锁)不兼容;但加上With(NoLock)后,SELECT不对查询到的资源加锁(或者加Sch-S锁,Sch-S锁可以与任何锁兼容);从而可以是这两条SQL可以并发地访问同一资源。当然,此方法适合解决读与写并发死锁的情况,但加With(NoLock)可能会导致脏读。

SELECT * FROM Lock2 WITH(NOLock)
SELECT * FROM Lock1 WITH(NOLock)

d). 使用较低的隔离级别。SQL Server 2000支持四种事务处理隔离级别(TIL),分别为:READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READSERIALIZABLESQL Server 2005中增加了SNAPSHOT TIL默认情况下,SQL Server使用READ COMMITTED TIL,我们可以在上面的两条SQL前都加上一句SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED,来降低TIL以避免死锁;事实上,运行在READ UNCOMMITTED TIL的事务,其中的SELECT语句不对结果资源加锁或加Sch-S锁,而不会加S锁;但还有一点需要注意的是:READ UNCOMMITTED TIL允许脏读,虽然加上了降低TIL的语句后,上面两条SQL在执行过程中不会报错,但执行结果是一个返回1,一个返回2,即读到了脏数据,也许这并不是我们所期望的。

e). SQL前加SET LOCK_TIMEOUT timeout_period,当请求锁超过设定的timeout_period时间后,就会终止当前SQL的执行,牺牲自己,成全别人。

f). 使用基于行版本控制的隔离级别(SQL Server 2005支持):开启下面的选项后,SELECT不会对请求的资源加S锁,不加锁或者加Sch-S锁,从而将读与写操作之间发生的死锁几率降至最低;而且不会发生脏读。

SET ALLOW_SNAPSHOT_ISOLATION ON
SET READ_COMMITTED_SNAPSHOT ON

       g). 使用绑定连接(使用方法见下一个示例。)

 

5.2 程序死锁(SQL阻塞)

看一个例子:一个典型的数据库操作事务死锁分析,按照我自己的理解,我觉得这应该算是C#程序中出现死锁,而不是数据库中的死锁;下面的代码模拟了该文中对数据库的操作过程:

复制代码

//略去的无关的code
SqlConnection conn = new SqlConnection(connectionString);
conn.Open();
SqlTransaction tran 
= conn.BeginTransaction();
string sql1 = "Update Lock1 SET C1=C1+1";
string sql2 = "SELECT * FROM Lock1";
ExecuteNonQuery(tran, sql1); 
//使用事务:事务中Lock了Table
ExecuteNonQuery(null, sql2); //新开一个connection来读取Table

public static void ExecuteNonQuery(SqlTransaction tran, string sql)
{
    SqlCommand cmd 
= new SqlCommand(sql);
    
if (tran != null)
    
{
        cmd.Connection 
= tran.Connection;
        cmd.Transaction 
= tran;
        cmd.ExecuteNonQuery();
    }

    
else
    
{
        
using (SqlConnection conn = new SqlConnection(connectionString))
        
{
            conn.Open();
            cmd.Connection 
= conn;
            cmd.ExecuteNonQuery();
        }

    }

}

复制代码

执行到ExecuteNonQuery(null, sql2)时抛出SQL执行超时的异常,下图从数据库的角度来看该问题:

     点击查看原图      

     代码从上往下执行,会话1持有了表Lock1X锁,且事务没有结束,回话1就一直持有X锁不释放;而会话2执行select操作,请求在表Lock1上加S锁,但S锁与X锁是不兼容的,所以回话2的被阻塞等待,不在等待中,就在等待中获得资源,就在等待中超时。。。从中我们可以看到,里面并没有出现死锁,而只是SELECT操作被阻塞了。也正因为不是数据库死锁,所以SQL Server的锁监视器无法检测到死锁。

       我们再从C#程序的角度来看该问题:

          点击查看原图 

       C#程序持有了表Lock1上的X锁,同时开了另一个SqlConnection还想在该表上请求一把S锁,图中已经构成了环路;太贪心了,结果自己把自己给锁死了。。。

       虽然这不是一个数据库死锁,但却是因为数据库资源而导致的死锁,上例中提到的解决死锁的方法在这里也基本适用,主要是避免读操作被阻塞,解决方法如下:

       a). SELECT放在Update语句前:SELECT不在事务中,且执行完毕会释放S锁;
       b). SELECT也放加入到事务中:ExecuteNonQuery(tran, sql2);
       c). SELECTWith(NOLock)提示:可能产生脏读;
       d). 降低事务隔离级别:SELECT语句前加SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;同上,可能产生脏读;
       e). 使用基于行版本控制的隔离级别(同上例)。
       g). 使用绑定连接:取得事务所在会话的token,然后传入新开的connection中;执行EXEC sp_bindsession @Token后绑定了连接,最后执行exec sp_bindsession null;来取消绑定;最后需要注意的四点是:
    
(1). 使用了绑定连接的多个connection共享同一个事务和相同的锁,但各自保留自己的事务隔离级别;
    
(2). 如果在sql3字符串的“exec sp_bindsession null”换成“commit tran”或者“rollback tran”,则会提交整个事务,最后一行C#代码tran.Commit()就可以不用执行了(执行会报错,因为事务已经结束了-,-)
    
(3). 开启事务(begin tran)后,才可以调用exec sp_getbindtoken @Token out来取得Token;如果不想再新开的connection中结束掉原有的事务,则在这个connection close之前,必须执行“exec sp_bindsession null”来取消绑定连接,或者在新开的connectoin close之前先结束掉事务(commit/tran)
    
(4). (Sql server 2005 联机丛书)后续版本的 Microsoft SQL Server 将删除该功能。请避免在新的开发工作中使用该功能,并着手修改当前还在使用该功能的应用程序。 请改用多个活动结果集 (MARS) 或分布式事务。

复制代码

tran = connection.BeginTransaction();
string sql1 = "Update Lock1 SET C1=C1+1";
ExecuteNonQuery(tran, sql1); 
//使用事务:事务中Lock了测试表Lock1
string sql2 = @"DECLARE @Token varchar(255);
exec sp_getbindtoken @Token out;
SELECT @Token;
";
string token = ExecuteScalar(tran, sql2).ToString();
string sql3 = "EXEC sp_bindsession @Token;Update Lock1 SET C1=C1+1;exec sp_bindsession null;";
SqlParameter parameter 
= new SqlParameter("@Token", SqlDbType.VarChar);
parameter.Value 
= token;
ExecuteNonQuery(
null, sql3, parameter); //新开一个connection来操作测试表Lock1
tran.Commit();

复制代码

 

 

附:锁兼容性(FROM SQL Server 2005 联机丛书)

锁兼容性控制多个事务能否同时获取同一资源上的锁。如果资源已被另一事务锁定,则仅当请求锁的模式与现有锁的模式相兼容时,才会授予新的锁请求。如果请求锁的模式与现有锁的模式不兼容,则请求新锁的事务将等待释放现有锁或等待锁超时间隔过期。
点击查看原图

 

sqlserver 内部函数、存储过程、角色

/*日期函数*/
DATEADD ( datepart , number, date )
--在向指定日期加上一段时间的基础上,返回新的 datetime 值。
DATEDIFF ( datepart , startdate , enddate )
--返回跨两个指定日期的日期和时间边界数。
DATENAME ( datepart , date )
--返回代表指定日期的指定日期部分的字符串。
DATEPART ( datepart , date )
--返回代表指定日期的指定日期部分的整数。
DAY ( date )
--返回代表指定日期的天的日期部分的整数。
GETDATE ( )
--按 datetime 值的 Microsoft? SQL Server? 标准内部格式返回当前系统日期和时间。
GETUTCDATE()
--返回表示当前 UTC 时间(世界时间坐标或格林尼治标准时间)的 datetime 值。
--当前的 UTC 时间得自当前的本地时间和运行 SQL Server 的计算机操作系统中的时区设置。
MONTH ( date )
--返回代表指定日期月份的整数。
YEAR ( date )
--返回表示指定日期中的年份的整数。
--------------------------------------------------------------------------
/*字符串处理函数*/
LCASE( )
LOWER( )
--将字符串转换为小写字母
LTRIM( )
--删除字符串前面的空格
SUBSTRING( )
--从字符串中提取一个或多个字符
UCASE( )
UPPER( )
--将字符串转换为大写字母
ROUND( )
--将数字按指定的小数位数四舍五入
FLOOR( )
--将数字向下四舍五入为最接近(最小)的整数
CEILING( )
--将数字向上四舍五入为最接近的整数
DATALENGTH( )
--返回指定的表达式所用的字节数
--------------------------------------------------------------------------
USER( )
USER_NAME( )
--返回当前用户名
CONVERT( )
--将数据从一种类型转换为另一种类型。
SOUNDEX( )
--为可创建"近似"搜索的指定表达式返回 Soundex 代码。
STR( )
--将数字数据转换为字符串,以便可以用文本运算符对其进行处理。
/*全局变量*/
@@CONNECTIONS
--服务器上次启动以来创建的连接数
@@CPU_BUSY 
--自 SQL Server 启动至今,系统持续运行的毫秒数。
@@CURSOR_ROWS 
--最近打开的游标中的行数
@@DATEFIRST  
--SET DATEFIRST 参数的当前值,该参数用于设置一个星期的第一天为哪一天。
@@ERROR 
--最后一个 T-SQL 错误的错误号
@@FETCH_STATUS
--如果最后一次提取的状态为成功状态,则为 0。如果出错,则为 -1
@@IDENTITY  
--最后一次插入的标识值
@@LANGUAGE  
--当前使用的语言的名称
@@MAX_CONNECTIONS
--可以创建的同时连接的最大数
@@ROWCOUNT  
--受上一个 SQL 语句影响的行数
@@SERVERNAME 
--本地服务器的名称
@@SERVICENAME 
--该计算机上的 SQL 服务的名称
@@TIMETICKS 
--当前计算机上每指令周期的微秒数
@@TRANSCOUNT 
--当前连接打开的事务数
@@VERSION  
--SQL Server 的版本信息
-----------------------------------------------------------------------
/*存储过程*/
sp_databases --列出服务器上的所有数据库
sp_server_info --列出服务器信息,如字符集,版本和排列顺序
sp_stored_procedures--列出当前环境中的所有存储过程
sp_tables --列出当前环境中所有可以查询的对象
sp_start_job --立即启动自动化任务
sp_stop_job --停止正在执行的自动化任务
sp_password --添加或修改登录帐户的密码
sp_configure --显示(不带选项)或更改(带选项)当前服务器的全局配置设置
sp_help --返回表的列名,数据类型,约束类型等
sp_helptext --显示规则,默认值,未加密的存储过程,用户定义的函数,
--触发器或视图的实际文本
sp_helpfile --查看当前数据库信息
sp_dboption --显示或更改数据库选项
sp_detach_db --分离数据库
sp_attach_db --附加数据库
sp_addumpdevice --添加设备
sp_dropdevice --删除设备
sp_pkeys --查看主键
sp_fkeys --查看外键
sp_helpdb --查看指定数据库相关文件信息
sp_addtype --自建数据类型
sp_droptype --删除自建数据类型
sp_rename --重新命名数据库
sp_executesql --执行SQL语句
sp_addlogin --添加登陆
sp_droplogin --删除登录
sp_grantdbaccess --把用户映射到登录,即添加一个数据库安全帐户并授予塔访问权限
sp_revokedbaccess--撤销用户的数据访问权,即从数据库中删除一个安全帐户
sp_addrole --添加角色
sp_addrolemember --向角色中添加成员,使其成为数据库角色的成员
sp_addsrvrolemember--修改登录使其成为固定服务器角色的成员
sp_grantlogin --允许使用组帐户或系统用户使用Windows身份验证连接到SQL
sp_defaultdb --修改一个登录的默认数据库
sp_helpindex --用于查看表的索引
sp_cursoropen --定义与游标和游标选项相关的SQL语句,然后生成游标
sp_cursorfetch --从游标中提取一行或多行
sp_cursorclose --关闭并释放游标
sp_cursoroption --设置各种游标选项
sp_cursor --用于请求定位更新
sp_cursorprepare --把与游标有关的T-SQL语句或批处理编译成执行计划,但并不创建游标
sp_cursorexecute --从由sp_cursorprepare创建的执行计划中创建并填充游标
sp_cursorunprepare --废弃由sp_cursorprepare生成的执行计划
sp_settriggerorder --指定第一个或最后一个激发的、与表关联的 AFTER 触发器。在第一个
--和最后一个触发器之间激发的 AFTER 触发器将按未定义的顺序执行
--------------------------------------------------------------------------------
/*服务器角色*/
sysadmin
--在 SQL Server 中进行任何活动。该角色的权限跨越所有其它固定服务器角色。
serveradmin
--配置服务器范围的设置。
setupadmin
--添加和删除链接服务器,并执行某些系统存储过程(如 sp_serveroption)。
securityadmin
--管理服务器登录。
processadmin
--管理在 SQL Server 实例中运行的进程。
dbcreator
--创建和改变数据库。
diskadmin
--管理磁盘文件。
bulkadmin
--执行 BULK INSERT 语句。
/*数据库角色*/
public
public 角色
--public 角色是一个特殊的数据库角色,每个数据库用户都属于它。public 角色:
--捕获数据库中用户的所有默认权限。
--无法将用户、组或角色指派给它,因为默认情况下它们即属于该角色。
--含在每个数据库中,包括 master、msdb、tempdb、model 和所有用户数据库。
--无法除去。
db_owner
--进行所有数据库角色的活动,以及数据库中的其它维护和配置活动。
--该角色的权限跨越所有其它固定数据库角色。
db_accessadmin
--在数据库中添加或删除 Windows NT 4.0 或 Windows 2000 组和用户以及 SQL Server 用户。
db_datareader
--查看来自数据库中所有用户表的全部数据。
db_datawriter
--添加、更改或删除来自数据库中所有用户表的数据
db_ddladmin
--添加、修改或除去数据库中的对象(运行所有 DDL)
db_securityadmin
--管理 SQL Server 2000 数据库角色的角色和成员,并管理数据库中的语句和对象权限
db_backupoperator
--有备份数据库的权限
db_denydatareader
--拒绝选择数据库数据的权限
db_denydatawriter
--拒绝更改数据库数据的权限

SQL Server 错误日志

sp_cycle_errorlog

 

错误日志文件暴增:

这里我说的不仅仅指某个错误日志记录文件暴增,也指目录Program Files\Microsoft SQL Server\MSSQL.n\MSSQL\LOG所占空间暴增,如果你平时都不关注这些错误日志,也从不维护错误日志记录文件,那么很有可能它所占的空间非常大,大到让你吃惊的地步。几十G的我也见过,那么具体原因可能有以下种:

 1:SQL 内部错误的时候会产生非常多的DUMP文件

2:高可用的数据库服务器可能很少停机,而你又没有定期清理、清空这些错误日志信息,那么ERRORLOG.n/SQLAGENT.n文件增长会非常大。这样对于DBA使用错误日志查找信息就会比较困难,而且日志大了加载、写入以后性能也会受到影响。

3:还有一个情况,如果你数据库IP地址曝露在外网时,会遭到大量尝试登录sa的攻击,也会产生大量的审核失败日志信息。

4:就是一些SQL SERVER PROFILE文件没有删除,当然,这个本质跟日志文件暴增没啥关系,但是跟LOG文件夹的大小有些关系。

如何清理错误日志:

对于SQL SERVER日志、SQL SERVER AGENT日志记录,微软提供了一个存储过程sp_cycle_errorlog可以实现日志的循环。 这个存储过程的作用是关闭当前的错误日志文件,并循环错误日志扩展编号(就像重新启动服务器)。每次启动SQL Server 时,都会将当前错误日志重命名为errorlog.1;errorlog.1 变为errorlog.2,errorlog.2 变为errorlog.3,依次类推。最后一个errorlog.n将会被删除。sp_cycle_errorlog 可使您循环访问错误日志文件,而不必停止和启动服务器。

另外:日志过大说明你没有截断错误日志,错误日志是可以截断的,进入你的数据库输入DBCC ERRORLOG

每执行一次,当前的错误日志退出,让后建立新的错误日志,你只能删除 ERRORLOGn的错误日志没有号码的是正在使用的日志,删除会报错,如果它比较大,就DBCC ERRORLOG,而后他会变成ERRORLOG+编号,你就可以删除了,另外建议你把这些ERRORLOG 放到其他盘符,比较好管理。