良心与思想

偶的代码生涯
随笔 - 32, 评论 - 243, 引用 - 31

导航

关于

最近在学习windbg

标签

每月存档

最新留言

广告

【第1页/共2页,32条】
首页
前页
1
2008年05月13日

 

Premier Field Engineer - SharePoint/MOSS

 

The purpose of the Premier Field Engineer (PFE) position is to provide Microsoft customers with reliable technical solutions to the complex integration problems associated with business solutions built using Windows SharePoint Services, SharePoint Portal Server and Microsoft Office SharePoint Server. Typical tasks performed in this role include specific problem isolation and correction, conducting application design and technical reviews, performance tuning, application stability consulting/troubleshooting, code reviews, porting/migration assistance, configuration/administration management, pre-rollout testing and general development consulting. The prospective PFE candidate should draw upon all resources at Microsoft, to advise and consult on the use of Microsoft SharePoint Technologies to avoid such problems in the future.

Requirements:

• Candidates must have exceptional customer service, problem solving, communication skills, and the ability to work in a team environment.
• Must have sufficient technical depth to communicate with development and other internal organizations at a peer level.
• Must possess the ability to work with minimal supervision and operate as a self contained business unit within the PFE team.
• Must demonstrate the aptitude for providing exceptional customer service in politically charged environments.
• Show the ability to enhance the technical expertise of peers via training development and delivery, mentoring of new hires, and team content development.
• Demonstrate strategic thinking with value-add contributions.
• Strong business background in Fortune 500 and/or experience with systems technology consulting firm desired.

Technical Requirements:
• Strong knowledge & technical proficiency with Microsoft SharePoint Technologies including Windows SharePoint Services, SharePoint Portal Server, & Microsoft Office SharePoint Server.
• Solid understanding of the Microsoft Windows platform and standard client/server, networking, and Internet fundamentals.
• Superior problem solving and troubleshooting skills at the System Engineer level.
• From this foundation, the PFE position is exposed to many technologies, including but not limited to: IIS, SQL Server, development with .NET languages, XML and Scripting technologies, and data access technologies.
• Practical user mode debugging is a preferred skill, but not required.

Travel requirements vary regionally. Engineers must be available for travel dispatch 24x7x365.

 
Education:
College degree, preferably in Computer Science, is required. MCTS in SharePoint Technologies or other applicable advanced certification is strongly preferred, but not required at hire; however, must be obtained within 12 months of hire. We will consider related field (or equivalent) experience.

Microsoft is an equal opportunity employer (EOE) and strongly supports diversity in the workforce.

 有意向的请和我联系:juqiang@live.com

posted on 2008-05-13 20:03:44 by juqiang  评论(1) 阅读(4697)

 
2008年03月13日
抓到一个mini dump,执行!clrstack,会提示如下错误:

*** WARNING: Unable to verify timestamp for mscorwks.dll
Failed to load data access DLL, 0x80004005
Verify that 1) you have a recent build of the debugger (6.2.14 or newer)
            2) the file mscordacwks.dll that matches your version of mscorwks.dll is
                in the version directory
            3) or, if you are debugging a dump file, verify that the file
                mscordacwks_<arch>_<arch>_<version>.dll is on your symbol path.
            4) you are debugging on the same architecture as the dump file.
                For example, an IA64 dump file must be debugged on an IA64
                machine.

You can also run the debugger command .cordll to control the debugger's
load of mscordacwks.dll.  .cordll -ve -u -l will do a verbose reload.
If that succeeds, the SOS command should work on retry.

If you are debugging a minidump, you need to make sure that your executable
path is pointing to mscorwks.dll as well.


解决办法很简单,把抓到的dump那个机器上的mscorwks拷过来,如在:c:\dumps\rules\debug目录下,那么执行:
0:037> .exepath+ c:\dumps\rules\debug
Executable image search path is: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727;c:\dumps\rules\debug


然后重新加载符号表即可:.reload

posted on 2008-03-13 12:29:36 by juqiang  评论(0) 阅读(3717)

 
2008年03月11日
对于XmlSerializer带来的内存占用过高,最终导致Out Of Memory的问题,参见以前这个链接:http://www.cnblogs.com/juqiang/archive/2008/01/15/1039936.html
(但是那篇文章中对于XmlSerializer构造方法的说明,是错误的。那段代码没有问题,有问题的是下面的)

首先看System.Xml.Serialization.XmlSerializer的构造方法,一共分为三大类:

public XmlSerializer(Type type) : this(type, (string) null)
public XmlSerializer(Type type, string defaultNamespace)
这两个方法,采用了上文引用的那篇文章的处理方式,应用了cache,这是正确的,不会造成内存占用过高。(上文引用那篇文章里面这个地方解释错了)

另一大类的方法是:
public XmlSerializer(XmlTypeMapping xmlTypeMapping),这个方法里面会产生一个tempAssembly,但是没有用缓存方式来处理。

有问题的一类方法是:
public XmlSerializer(Type type, Type[] extraTypes) : this(type, null, extraTypes, null, null, null, null)
public XmlSerializer(Type type, XmlAttributeOverrides overrides) : this(type, overrides, new Type[0], null, null, null, null)
public XmlSerializer(Type type, XmlRootAttribute root) : this(type, null, new Type[0], root, null, null, null)
public XmlSerializer(Type type, XmlAttributeOverrides overrides, Type[] extraTypes, XmlRootAttribute root, string defaultNamespace) : this(type, overrides, extraTypes, root, defaultNamespace, null, null)
public XmlSerializer(Type type, XmlAttributeOverrides overrides, Type[] extraTypes, XmlRootAttribute root, string defaultNamespace, string location, Evidence evidence)

前4个都最终调用了最后一个构造方法,里面产生了一个tempAssembly,也没有用缓存方式来护理。

我上次提到的那个问题,问题代码如下:
public static TReturn Convert<TReturn, TInput>(TInput input) where TReturn: class, new() where TInput: IProvisioningObject
{
    using (MemoryStream stream = new MemoryStream())
    {
        new XmlSerializer(typeof(TInput)).Serialize((Stream) stream, input);
        stream.Position = 0L;
        XmlSerializer serializer = new XmlSerializer(typeof(TReturn), new XmlRootAttribute(input.GetType().Name));
        return (TReturn) serializer.Deserialize(stream);
    }
}
注意红色的代码,这里产生了一个tempAssembly,没有做缓存。这里的核心问题在于,tempAssembly不会被自动释放掉,除非appdomain被unload。
修正的方式类似于XmlSerializer的处理方式,采用一个二元Hashtable来做。代码修改如下:
 1 private static TempXmlSerializerCache cache = new TempXmlSerializerCache();
 2
 3        public static TReturn Convert<TReturn, TInput>(TInput input)
 4            where TReturn : classnew()
 5            where TInput : IProvisioningObject
 6        {
 7            using (MemoryStream stream = new MemoryStream())
 8            {
 9                new XmlSerializer(typeof(TInput)).Serialize((Stream)stream, input);
10                stream.Position = 0L;
11
12                XmlSerializer serializer = cache[typeof(TReturn).ToString(), input.GetType().Name];
13                if (serializer == null)
14                {
15                    lock (cache)
16                    {
17                        serializer = cache[typeof(TReturn).ToString(), input.GetType().Name];
18                        if (serializer == null)
19                        {
20                            serializer = new XmlSerializer(typeof(TReturn), new XmlRootAttribute(input.GetType().Name));
21                            cache.Add(typeof(TReturn).ToString(), input.GetType().Name, serializer);
22                        }

23                    }

24                }

25                return (TReturn)serializer.Deserialize(stream);
26            }

27        }

这里的二元Hashtable,是从XmlSerializer里面扒出来的,稍微修改了一下而已:

    public class TempXmlSerializerCache
    
{
        
private Hashtable cache = new Hashtable();

        
public void Add(string ns, object o, XmlSerializer serializer)
        
{
            XmlSerializerCacheKey key 
= new XmlSerializerCacheKey(ns, o);
            
lock (this)
            
{
                
if (this.cache[key] != serializer)
                
{
                    Hashtable hashtable 
= new Hashtable();
                    
foreach (object obj2 in this.cache.Keys)
                    
{
                        hashtable.Add(obj2, 
this.cache[obj2]);
                    }

                    
this.cache = hashtable;
                    
this.cache[key] = serializer;
                }

            }

        }


        
public XmlSerializer this[string ns, object o]
        
{
            
get
            
{
                
return (XmlSerializer)this.cache[new XmlSerializerCacheKey(ns, o)];
            }

        }

    }


以及:
   public class XmlSerializerCacheKey
    
{
        
private string ns;
        
private object type;

        
public XmlSerializerCacheKey(string ns, object type)
        
{
            
this.type = type;
            
this.ns = ns;
        }


        
public override bool Equals(object o)
        
{
            XmlSerializerCacheKey key 
= o as XmlSerializerCacheKey;
            
if (key == null)
            
{
                
return false;
            }

            
return ((key.type == this.type) && (key.ns == this.ns));
        }


        
public override int GetHashCode()
        
{
            
return (((this.ns != null? this.ns.GetHashCode() : 0^ ((this.type != null? this.type.GetHashCode() : 0));
        }

    }

posted on 2008-03-11 18:52:35 by juqiang  评论(1) 阅读(3589)

 
2008年03月10日

    网站升级后,说有High CPU的问题。mstsc上去后,看了一下,果然如此,w3wp.exe的cpu几乎一直是满的,我的远程桌面操作也很慢。下载下来windbg,装好,抓了两个dump。首先!runaway一下,

0:022> !runaway
 User Mode Time
  Thread       Time
  22:8cc       0 days 0:17:15.238
  23:4d8       0 days 0:15:20.936
  15:898       0 days 0:09:15.316
  24:c64       0 days 0:05:07.587
这是前4个线程,一共占用了47分钟左右,我们再看一下time的时间:

0:022> .time
Debug session time: Sun Mar  9 15:07:52.000 2008 (GMT+8)
System Uptime: 1 days 2:04:59.903
Process Uptime: 0 days 0:55:38.000
  Kernel time: 0 days 0:00:07.000
  User time: 0 days 0:48:41.000

cool,.time的时间和!runaway的几乎一致,再看看!threadpool的数字:

0:022> !threadpool
CPU utilization 100%
Worker Thread: Total: 5 Running: 5 Idle: 0 MaxLimit: 200 MinLimit: 2
Work Request in Queue: 15
Unknown Function: 6a2aa6fb  Context: 0190c5b8
AsyncTimerCallbackCompletion TimerInfo@01969b30
AsyncTimerCallbackCompletion TimerInfo@01969428
Unknown Function: 6a2aa6fb  Context: 0190ccb8
AsyncTimerCallbackCompletion TimerInfo@019698a8
AsyncTimerCallbackCompletion TimerInfo@01969c98
AsyncTimerCallbackCompletion TimerInfo@019f5778
AsyncTimerCallbackCompletion TimerInfo@01918658
AsyncTimerCallbackCompletion TimerInfo@01969788
Unknown Function: 6a2aa6fb  Context: 0190cb38
AsyncTimerCallbackCompletion TimerInfo@019695d8
Unknown Function: 6a2aa6fb  Context: 0190caf8
AsyncTimerCallbackCompletion TimerInfo@01969740
Unknown Function: 6a2aa6fb  Context: 0190c178
Unknown Function: 6a2aa6fb  Context: 0190cbf8
--------------------------------------
Number of Timers: 9
--------------------------------------
Completion Port Thread:Total: 2 Free: 2 MaxFree: 4 CurrentLimit: 2 MaxLimit: 200 MinLimit: 2

这些信息基本够了,罪魁祸首就是!runaway跑的那几个线程,我们随便抓两个来看看:

0:022> kb
ChildEBP RetAddr  Args to Child             
00005205 7a4c72c0 00000000 00000000 00000000 System_ni!System.Text.RegularExpressions.RegexCharClass.CharInClassRecursive(Char, System.String, Int32)+0x20
00005205 7a4b16e5 00000000 00000000 00000000 System_ni!System.Text.RegularExpressions.RegexInterpreter.Go()+0xc40
00005205 7a4b15c3 00000000 00000000 00000000 System_ni!System.Text.RegularExpressions.RegexRunner.Scan(System.Text.RegularExpressions.Regex, System.String, Int32, Int32, Int32, Int32, Boolean)+0xa5
00005205 7a4b14af 00000000 00000000 00000000 System_ni!System.Text.RegularExpressions.Regex.Run(Boolean, Int32, System.String, Int32, Int32, Int32)+0x103
ffffffff 7a4dc313 00000000 00000000 00000000 System_ni!System.Text.RegularExpressions.Regex.Match(System.String, Int32)+0x1f
ffffffff 7a4d1c39 00000000 00000000 00000000 System_ni!System.Text.RegularExpressions.RegexReplacement.Replace(System.Text.RegularExpressions.Regex, System.String, Int32, Int32)+0x47
0606f8ac 7a4d1b53 00000000 00000000 00000000 System_ni!System.Text.RegularExpressions.Regex.Replace(System.String, System.String, Int32, Int32)+0x99
05d8ed04 0ff965c7 00000000 00000000 00000000 System_ni!System.Text.RegularExpressions.Regex.Replace(System.String, System.String)+0x2f
05d8ed04 0ff96530 00000000 00000000 00000000 3rdparty_Framework!3rdparty.Framework.Text.HtmlHelper.RemoveHtmlComments(System.String)+0x67
00000000 0ff96477 00000000 00000000 00000000 3rdparty_Framework!3rdparty.Framework.Text.HtmlHelper.RemoveHtmlAndComments(System.String)+0x38
10c3e8a8 0ff96142 00000000 00000000 00000000 3rdparty_Framework!3rdparty.Framework.Components.AggregatedPost.set_Description(System.String)+0x2f
10c3e8a8 0ff95d8e 00000000 00000000 00000000 3rdparty_Framework!3rdparty.Framework.Data.DataHelper.LoadAggreatedPost(System.Data.IDataReader)+0x1e2
10c3e910 0ff95c02 00000000 00000000 00000000 3rdparty_Framework!3rdparty.Framework.Data.DatabaseObjectProvider.GetAggregatedPosts(Int32, Int32, Int32)+0x126
10c3e958 0ff9e4eb 00000000 00000000 00000000 3rdparty_Framework!3rdparty.Framework.Data.Cacher.GetPagedAggregatedPosts(Int32, Int32, Int32, 3rdparty.Framework.CacheDuration, Boolean)+0x14a
10c3e9b0 0ff9e364 00000000 00000000 00000000 3rdparty_Blog_Web!3rdparty.Blog.Web.WebForm1.BindData()+0x16b
10c3ec04 66f12980 00000000 00000000 00000000 3rdparty_Blog_Web!3rdparty.Blog.Web.WebForm1.Page_Load(System.Object, System.EventArgs)+0x44
10c3ec04 6628eff2 00000000 00000000 00000000 System_Web_RegularExpressions_ni!System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs)+0x10
10c3ec04 6613cb24 00000000 00000000 00000000 System_Web_ni!System.Web.Util.CalliEventHandlerDelegateProxy.Callback(System.Object, System.EventArgs)+0x22
10c3ec04 6613cb70 00000000 00000000 00000000 System_Web_ni!System.Web.UI.Control.OnLoad(System.EventArgs)+0x64
10c3ec04 6614e14d 00000000 00000000 00000000 System_Web_ni!System.Web.UI.Control.LoadRecursive()+0x30

再看看其他线程的:

0:015> kb
ChildEBP RetAddr  Args to Child             
0000515e 7a4b16e5 00000000 00000000 00000000 System_ni!System.Text.RegularExpressions.RegexInterpreter.Go()+0x152b
0000515e 7a4b15c3 00000000 00000000 00000000 System_ni!System.Text.RegularExpressions.RegexRunner.Scan(System.Text.RegularExpressions.Regex, System.String, Int32, Int32, Int32, Int32, Boolean)+0xa5
0000515e 7a4b14af 00000000 00000000 00000000 System_ni!System.Text.RegularExpressions.Regex.Run(Boolean, Int32, System.String, Int32, Int32, Int32)+0x103
ffffffff 7a4dc313 00000000 00000000 00000000 System_ni!System.Text.RegularExpressions.Regex.Match(System.String, Int32)+0x1f
ffffffff 7a4d1c39 00000000 00000000 00000000 System_ni!System.Text.RegularExpressions.RegexReplacement.Replace(System.Text.RegularExpressions.Regex, System.String, Int32, Int32)+0x47
05f79678 7a4d1b53 00000000 00000000 00000000 System_ni!System.Text.RegularExpressions.Regex.Replace(System.String, System.String, Int32, Int32)+0x99
05db3568 0ff965c7 00000000 00000000 00000000 System_ni!System.Text.RegularExpressions.Regex.Replace(System.String, System.String)+0x2f
05db3568 0ff96530 00000000 00000000 00000000 3rdparty_Framework!3rdparty.Framework.Text.HtmlHelper.RemoveHtmlComments(System.String)+0x67
00000000 0ff96477 00000000 00000000 00000000 3rdparty_Framework!3rdparty.Framework.Text.HtmlHelper.RemoveHtmlAndComments(System.String)+0x38
0140f0d8 0ff96142 00000000 00000000 00000000 3rdparty_Framework!3rdparty.Framework.Components.AggregatedPost.set_Description(System.String)+0x2f
0140f0d8 0ff95d8e 00000000 00000000 00000000 3rdparty_Framework!3rdparty.Framework.Data.DataHelper.LoadAggreatedPost(System.Data.IDataReader)+0x1e2
0140f140 0ff95c02 00000000 00000000 00000000 3rdparty_Framework!3rdparty.Framework.Data.DatabaseObjectProvider.GetAggregatedPosts(Int32, Int32, Int32)+0x126
0140f188 0ff9e4eb 00000000 00000000 00000000 3rdparty_Framework!3rdparty.Framework.Data.Cacher.GetPagedAggregatedPosts(Int32, Int32, Int32, 3rdparty.Framework.CacheDuration, Boolean)+0x14a
0140f1e0 0ff9e364 00000000 00000000 00000000 3rdparty_Blog_Web!3rdparty.Blog.Web.WebForm1.BindData()+0x16b
0140f434 66f12980 00000000 00000000 00000000 3rdparty_Blog_Web!3rdparty.Blog.Web.WebForm1.Page_Load(System.Object, System.EventArgs)+0x44
0140f434 6628eff2 00000000 00000000 00000000 System_Web_RegularExpressions_ni!System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs)+0x10
0140f434 6613cb24 00000000 00000000 00000000 System_Web_ni!System.Web.Util.CalliEventHandlerDelegateProxy.Callback(System.Object, System.EventArgs)+0x22
0140f434 6613cb70 00000000 00000000 00000000 System_Web_ni!System.Web.UI.Control.OnLoad(System.EventArgs)+0x64
0140f434 6614e14d 00000000 00000000 00000000 System_Web_ni!System.Web.UI.Control.LoadRecursive()+0x30
0140f434 6614d8e3 00000000 00000000 00000000 System_Web_ni!System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)+0x59d

其他两个线程和这两个类似。

偶感觉是在太幸运了,居然碰到这么一个simple的high cpu,哈哈!那看一下,为什么Regex.Replace会慢?运行一下!clrstack -a命令:

0:015> !clrstack -a
OS Thread Id: 0x898 (15)
ESP       EIP   

====================其他太长,省略……=============================

0140f034 7a4d1b53 System.Text.RegularExpressions.Regex.Replace(System.String, System.String)
    PARAMETERS:
        this = <no data>
        input = <no data>
        replacement = <no data>

0140f044 0ff965c7 Joycode.Framework.Text.HtmlHelper.RemoveHtmlComments(System.String)
    PARAMETERS:
        text = 0x05da9298
    LOCALS:
        <CLR reg> = 0x05db3568
        <CLR reg> = 0x00000000
        0x0140f044 = 0x00000000

====================其他太长,省略……=============================

哦,那个字符串的地址是0x05da9298,看看大小,是41K

0:015> !objsize 0x05da9298
sizeof(05da9298) =        41,680 (      0xa2d0) bytes (System.String)

如果do这个字符串的话,我们能看到它是一个html文本。因为runaway和.time和threadpool的数据太明显,so,断定问题就在于replace的字符串太大或者频度太高造成的。

后来经过确认,RemoveHtmlComments代码中的Regex.Replace没有用,so,直接comment掉了,问题解决。

额外看一下别的东西:

 

Address         MT     Priority         Create Time             Expires        Last Updated  Key Class Name
01b1c6cc  6639d580 Normal 03/09/2008 06:12:14 12/31/9999 23:59:59 0   0  dmachine/webroot/2/app_code System.Web.CachedPathData
01b1f470 6639d580 Normal  03/09/2008 06:12:14  12/31/9999 23:59:59  0 dmachine/webroot/2/global.asax System.Web.CachedPathData
01b3b3b0 6639d580 Normal  03/09/2008 06:12:15  12/31/9999 23:59:59 03/09/2008 07:07:35  dmachine/webroot/2/systemmessages System.Web.CachedPathData

这是aspnetcache的很少一部分的内容,值得注意的是,有很多cache的内容,过期时间是9999年。实际上,我们有很大理由可以判断,“天长日久”,w3wp会因为Out Of Memory而导致recycled问题,或者是访问量骤增并持续一段时间,会导致网站短期内崩掉。

再看一下heap:

0:015> !heapstat
Heap     Gen0         Gen1         Gen2         LOH
Heap0    7291624      255092       5071228      13381296   
Heap1    7178448      440428       4637748      14825768   
Total    14470072     695520       9708976      28207064   

Free space:                                                 Percentage
Heap0    84           12           6568         2011792     SOH:  0% LOH: 15%
Heap1    2588         12           392          7699728     SOH:  0% LOH: 51%
Total    2672         24           6960         9711520    

大对象是最大的,,几乎等于gen0+gen1+gen2的总和。我们看看整体情况先:

 0:015> .foreach(myobj {!dumpheap -short -min 85000}) {!objsize myobj}
sizeof(09c07bf0) =      689,900 (     0xa86ec) bytes (System.Byte[])
sizeof(09cb02e0) =      616,976 (     0x96a10) bytes (System.Byte[])
sizeof(09e2f2a8) =      616,976 (     0x96a10) bytes (System.Byte[])
====================其他太长,省略……=============================
sizeof(0c89cf50) =      616,976 (     0x96a10) bytes (System.Byte[])
sizeof(09d7c320) =      131,084 (     0x2000c) bytes (System.Int32[])
====================其他太长,省略……=============================
sizeof(0bec8f60) =    1,114,124 (    0x11000c) bytes (System.Int32[])
sizeof(0c092a68) =    1,114,124 (    0x11000c) bytes (System.Int32[])
sizeof(09b8cb40) =      105,512 (     0x19c28) bytes ()
sizeof(09bc6780) =      263,280 (     0x40470) bytes ()
====================其他太长,省略……=============================
sizeof(0c42f080) =    4,644,560 (    0x46ded0) bytes ()
sizeof(09b26468) =      262,164 (     0x40014) bytes (System.String)
====================其他太长,省略……=============================
sizeof(0bda0f00) =      262,164 (     0x40014) bytes (System.String)
sizeof(0bfd8f70) =      262,164 (     0x40014) bytes (System.String)

byte数组:

0:015> !gcroot 0c398670
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 7 OSThread c60
Scan Thread 13 OSThread aec
Scan Thread 14 OSThread c68
Scan Thread 15 OSThread 898
ESP:140f088:Root:  05da5d54(System.Data.SqlClient.SqlDataReader)->
  05da5a18(System.Data.SqlClient.SqlConnection)->
  01d3a928(System.Data.SqlClient.SqlInternalConnectionTds)->
  05b84ccc(System.Data.ProviderBase.DbConnectionPool)->
  05b84f9c(System.Collections.Generic.List`1[[System.Data.ProviderBase.DbConnectionInternal, System.Data]])->
  05b84fb4(System.Object[])->
  05e03e00(System.Data.SqlClient.SqlInternalConnectionTds)->
  05e03e9c(System.Data.SqlClient.TdsParser)->
  05e03f3c(System.Data.SqlClient.TdsParserStateObject)->
  0c398670(System.Byte[])
ESP:140f0e8:Root:  05da5d54(System.Data.SqlClient.SqlDataReader)->
  05da5a18(System.Data.SqlClient.SqlConnection)->
  01d3a928(System.Data.SqlClient.SqlInternalConnectionTds)->
  05b84ccc(System.Data.ProviderBase.DbConnectionPool)->
  05b84f9c(System.Collections.Generic.List`1[[System.Data.ProviderBase.DbConnectionInternal, System.Data]])->
  05b84fb4(System.Object[])->
  05e03e00(System.Data.SqlClient.SqlInternalConnectionTds)->
  05e03e9c(System.Data.SqlClient.TdsParser)->
  05e03f3c(System.Data.SqlClient.TdsParserStateObject)->
  0c398670(System.Byte[])

其他的bye数组类似,都是在执行SqlDataReader的操作。(为啥这样,这个callstack看不到)

看int32数组:

0:015> !gcroot 09fd4e60
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 7 OSThread c60
====================其他太长,省略……=============================
Scan Thread 24 OSThread c64
ecx:Root:  01e51c1c(System.Text.RegularExpressions.RegexInterpreter)->
  09fd4e60(System.Int32[])
esi:Root:  01e51c1c(System.Text.RegularExpressions.RegexInterpreter)->
  09fd4e60(System.Int32[])
ESP:114ae598:Root:  01e51c1c(System.Text.RegularExpressions.RegexInterpreter)->
  09fd4e60(System.Int32[])
ESP:114ae5ac:Root:  01e51c1c(System.Text.RegularExpressions.RegexInterpreter)->
  09fd4e60(System.Int32[])
Scan Thread 26 OSThread 9ac
Scan Thread 27 OSThread 80c

这个是上面分析的正则表达式。

字符串的信息,就是上面看到的,都是html reply。针对这几种类型,我们做一个总结(在所有大对象中)

byte数组:4M,这个是sqldatareader读出来的数据

int32数组:5.8M,这个是因为regex和下面的string导致的

string:8.5M

另外:


上述所有module都是debug模式的,这里需要我们修改一下web.config,设置debug=false以及batch compilation为true,这样也能一定程度上提高performance。

没啥结论,就这么结束了。

0:015> !finddebugmodules
Loading all modules.
Searching for modules built in debug mode...

App_GlobalResources.-mywaxaw.dll not built release
App_Code.fqhaeca6.dll not built release
3rdparty.Framework.DLL not built release
App_global.asax.0y1c1i6p.dll not built release
3rdparty.Blog.Web.DLL not built release
freetextbox.DLL not built release
3rdparty.BlogML.DLL not built release
3rdparty.Extensibility.DLL not built release
3rdparty.Installation.DLL not built release
3rdparty.Plugins.Core.DLL not built release
3rdparty.Plugins.Examples.DLL not built release
3rdparty.Scripting.DLL not built release
3rdparty.Web.Controls.DLL not built release
App_Web_lab8my1c.dll not built release
App_Theme_Home.Clever.__mjadjg.dll not built release
App_Web_xppxn64l.dll not built release
App_Web_yxlgarwj.dll not built release
App_Web_a8wjdvdv.dll not built release
App_Web_ijqvv7ky.dll not built release
App_Web_xxkqxoen.dll not built release
App_Web_b25tsu-i.dll not built release
App_Web_kcb_q9mh.dll not built release
App_Theme_theme.elitecircle.jmea1orn.dll not built release
App_Web_site.master.9fb3aa03.6svjtsyu.dll not built release
App_GlobalResources.-mywaxaw.resources.dll not built release
App_Theme_theme.default.rggbez76.dll not built release
App_Web_post.aspx.9fb3aa03.ocnogniv.dll not built release
App_Web_k_ivm-xy.dll not built release
App_Web_category.aspx.9fb3aa03.lq_3jsov.dll not built release
App_Web_r8dwq35a.dll not built release
App_Web_archivemonth.aspx.9fb3aa03.jkc9sbgz.dll not built release
App_Web_gallery.aspx.9fb3aa03.vc-q4rsf.dll not built release
App_Web_contact.aspx.9fb3aa03.ayhi_kzk.dll not built release
App_Web_vocni5-f.dll not built release
App_Web_zgfpt_vo.dll not built release

Done Seaching

posted on 2008-03-10 16:34:33 by juqiang  评论(4) 阅读(3341)

 
2007年11月13日

Paint事件被触发了多少次?比较简单的方式,我们自己做一个perfmon能用的counter。看代码:

 1        private void button1_Click(object sender, EventArgs e)
 2        {
 3            
 4            if (!PerformanceCounterCategory.Exists("GDI+ Monitor"))
 5            {
 6                CounterCreationDataCollection ccdc = new CounterCreationDataCollection();
 7
 8                CounterCreationData totalPaint = new CounterCreationData();
 9                totalPaint.CounterName = "# operator executed";
10                totalPaint.CounterHelp = "Counts of OnPaint events called";
11                totalPaint.CounterType = PerformanceCounterType.NumberOfItems32;
12
13                ccdc.Add(totalPaint);
14
15                PerformanceCounterCategory.Create("GDI+ Monitor""Some counters for GDI+ objects", PerformanceCounterCategoryType.MultiInstance, ccdc);
16            }

17        }

好,点一下button1之后,我们就可以在perfmon中看到GDI+ Monitor这个category了,然后只有一个counter,就是# operator executed

然后在winform中增加这么一段:
           paintcall = new PerformanceCounter();
            paintcall.CategoryName 
= "GDI+ Monitor";
            paintcall.CounterName 
= "# operator executed";
            paintcall.MachineName 
= ".";
            paintcall.ReadOnly 
= false;

好了,代码里面可以用paintcall这个变量了。我们现在的需求是监视Paint被触发了多少次,那么可以在代码中这么写:
 1            Graphics g2 = e.Graphics;
 2
 3            Bitmap bmp = new Bitmap(this.Width, this.Height);
 4            Graphics g = Graphics.FromImage(bmp);
 5
 6            paintcall.Increment();
 7
 8            Rectangle r = new Rectangle(00this.Width, this.Height);
 9            g.FillRectangle(new LinearGradientBrush(r, Color.Red, Color.Blue, LinearGradientMode.BackwardDiagonal), r);
10
11            g2.DrawImage(bmp, new Point(00));
12
13            g.Dispose();
14            g = null;
15
16            bmp.Dispose();
17            bmp = null;

看上面第6行,这句会把paint call增加一。当然,其他方法有很多,这里不写了。
好,跑一下perfmon,然后把我们新增加的counter add上,嗯,可以看到当窗口无效的时候,计数器就增加了。

有一个小的细节,当窗口稍微改动大小的时候,你会发现Paint被调用了多次,这个实在很郁闷。so,稍微做点手脚:
 1        private bool resize = false;
 2        private void Form1_ResizeEnd(object sender, EventArgs e)
 3        {
 4            resize = false;
 5            this.Invalidate();
 6        }

 7
 8        private void Form1_ResizeBegin(object sender, EventArgs e)
 9        {
10            resize = true;
11        }

然后我们修改一下Paint事件的代码如下:
 1        private void Form1_Paint(object sender, PaintEventArgs e)
 2        {
 3            if (true == resize) return;
 4
 5            Graphics g2 = e.Graphics;
 6
 7            Bitmap bmp = new Bitmap(this.Width, this.Height);
 8            Graphics g = Graphics.FromImage(bmp);
 9
10            paintcall.Increment();
11
12            Rectangle r = new Rectangle(00this.Width, this.Height);
13            g.FillRectangle(new LinearGradientBrush(r, Color.Red, Color.Blue, LinearGradientMode.BackwardDiagonal), r);
14
15            g2.DrawImage(bmp, new Point(00));
16
17            g.Dispose();
18            g = null;
19
20            bmp.Dispose();
21            bmp = null;
22        }

注意第三行,我们判断,如果在resize过程中,那么就直接返回。这样,虽然界面像白板一样,但是却少了很多Paint操作,从性能上会好不少。

posted on 2007-11-13 16:54:00 by juqiang  评论(4) 阅读(5015)

 
2007年11月10日

GDI+自身是否有leak,我们不去管,现在说的是.NET代码中的处理。
首先看我这个简单的helper

using System;
using System.Diagnostics;
using System.Text;
using System.Runtime.InteropServices;

public class MemoryReport{
    [DllImport(
"user32.dll", CharSet=CharSet.Auto)]
    
public static extern long GetGuiResources(IntPtr hProcess, long flag);

    
public static string Write(){
        Process p 
= Process.GetCurrentProcess();
        ing hcount 
= p.HandleCount;
        
long psize = p.PrivateMemorySize64;
        
long vsize = p.VirtualMemorySize64;
        
long workset = p.WorkingSet64;
        
long gcsize = GC.GetTotalMemory(false);
        
int gdiobjs = (int)(GetGdiResources(p.Handle,0));
        
int userobjs = (int)(GetGdiResources(p.Handle,1));

        
return String.Format("Handle count:{0:N0},Private Bytes:{1:N0}K, Virtual Bytes:{2:N0}K, Working Set:{3:N0}K, GC Heap Size:{4:N0}K, GDI Objects:{5:N0}, User Objects:{6:N0}", hcount, psize>>10, vsize>>10, workset>>10, gcsize>>10, gdiobjs, userobjs);
}

        
    }

现在我们做一个winform程序,放一个button,在click里面写如下测试代码:
for(int i=0;i<1000;i++){
    Bitmap b 
= new Bitmap("c:\\1.bif");
    IntPtr ip 
= b.GetHbitmap();
    Bitmap b2 
= Bitmap.FromHbitmap(ip);
}


MessageBox.Show(MemoryReport.Write());

观察每次的结果,Private Bytes/ Virtual Bytes/ Working Set基本是一个上涨的走向。但是我们感兴趣的是这几个地方:
1、Handle count:这个值一般会波动变化,在这里例子里面,你把程序运行起来后,用taskmgr来观察Handle Count一栏(默认的没有,需要你自己手工添加这个column),一般是100以下。然后点一下按钮,handle count会增长1000左右,再点几次,会在1000上下波动,不会继续增长。
2、GDI Objects:这个值每次会增加1000
3、你连续点10次这个button,嘣!程序crash了。。。如果看dump里面的异常,会是什么bitmap的一个构造方法的parameter不正确。
4、GC Heap Size很小很小,我这里是2M。但是virtual size很大。

对于1,为什么这样,我不清楚;对于2,原因在于GetHbitmap返回的是一个Unmanged resource,GC不会回收(即使你使用了GC.Collect()这个值也不会下降的);对于3,OS默认的每个process的GDI objects上限为10000个,我们代码中是循环了1000次,所以如果你点了10次button,程序就会完蛋。对于4,说明leak的资源是unmanged resource,so,gc heap看起来很乖。

那么,如何修复上面的问题2?既然是unmanged resource,我们就要从unmanged找起。
[DllImport("gdi32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr DeleteObject(IntPtr hobj);
for(int i=0;i<1000;i++){
    Bitmap b 
= new Bitmap("c:\\1.bif");
    IntPtr ip 
= b.GetHbitmap();
    Bitmap b2 
= Bitmap.FromHbitmap(ip);

     DeleteObject(ip);
}


MessageBox.Show(MemoryReport.Write());

嗯,再运行一次,好了!GDI objects稳定了,再也没有变化过。
不过,我们修改一下循环计数器,到5000吧,然后观察Handle count,波动的比较厉害,内存相关的三组数值也稍有变化。好,我们再修改一次程序
[DllImport("gdi32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr DeleteObject(IntPtr hobj);
for(int i=0;i<1000;i++){
    Bitmap b 
= new Bitmap("c:\\1.bif");
    IntPtr ip 
= b.GetHbitmap();
    Bitmap b2 
= Bitmap.FromHbitmap(ip);

     b.Dispose();
     b2.Dispose();

     DeleteObject(ip);
}


MessageBox.Show(MemoryReport.Write());

重新run一次,嗯,这个世界终于清静了,handle count/gdi resource/ mem size都很平稳。

so,总结一下,对于类似上面的、可能被反复调用的type,如GDI+ obj,可以考虑使用完毕后立刻Dispose,这样可以被GC提早回收。对于返回一个IntPtr的方法,要仔细看,是不是需要再call win32里面对应的Delete方法。

对于绝大多数GDI+ obj,我们只需要DeleteObject即可,但是对于icon,我记着是另外一个函数,有兴趣的可以在msdn上查一下。

posted on 2007-11-10 10:08:00 by juqiang  评论(11) 阅读(4786)

 
2007年11月09日

1、这本书对于初学者没有太大用处
2、这本书对于眼中只有架构、自己不写程序的、鄙视代码的人没有用处
3、这本书对于非微软的人用处不算太大,你不知道ms内部的数据结构,你没有private symbols。
4、这本书对于微软的人用处不算太大,搞debug的就那么几号人

5、这本书对于在客户现场被骂的狗血喷头的、自己即使架了.NET IDE也不知道如何找出问题的人很有用处

 

如果你是第五种人,疯狂购买吧!

posted on 2007-11-09 11:13:00 by juqiang  评论(4) 阅读(4476)

 
2007年11月08日
此文是偶的偶像和哥们的,转过来,替他做一下宣传。偶会买10本,9本送人,有要的,现在报名。
(原来公司大量的COM+和.NET的case,都是熊老大做的)
 
Windows 高效排错
《Windows 高效排错》 可以在CSDN读书频道预览了
读书频道的排版有些问题,看起来不是很舒服。如果想看PDF的,可以在这里下载
纸板书籍估计在11月中下旬面世 
现在在China-pub, dearbook等网上书店已经有介绍,地址分别
您的看法非常重要。如果你看过这本书的PDF,了解本书的内容和潜在读者,请您在上面的链接中留下你的观点,便于其它不了解这本书的人选择。
如果您通过这本书介绍的方法解决过实际的问题,非常希望您能够分享您的经验。您可以发邮件到eparg@msn.com。如果您的分享能够帮助其它读者,我非常希望能够送书给您作为答谢。
如果您对书中所述内容有疑惑,也欢迎您写信来讨论。我会尽快回复。如果我们之间的讨论对其他读者有帮助,我也会放到网上,同时送书给您作为答谢。
===
上面是官腔了。我个人想说的话其实是:
1) 书前言里面说,"2007年年初我不再做技术支持。希望这本书能帮我记录下这一段美好的经历",其中最让我难忘的就是跟xiao,elan和leo站在水房门口讨论case的日子
2) 常来我blog踩的同学们,你们要书的话把地址留给我。等我拿到了我给大家发。(估计要等一段时间。出版社就给我6本。剩下的我争取去批发)
3) 跟我一起做过case的,如果你需要的话也把地址留给我
4) 多卖一本书,我可以多拿3块钱不到。整本书的稿费还不够在上海买半个车牌,或者买个厕所。我一点也不关心字面上的销量。我之所以花那么多时间来做这个亏本生意,目的只有一个,就是希望跟喜欢调试的人分享其中的过程,让真正需要这方面文章的人有所参考。所以如果你身边有这本书的潜在读者,劳烦你推荐一下。

posted on 2007-11-08 10:54:00 by juqiang  评论(27) 阅读(5041)

 
2007年08月08日

背景:两个CustomControl,一个叫做MyPanel,是一个M*N的格子,一个叫做Ball,是一个球。球可以放在panel的格子中,可以鼠标进行drag & drop.

直接call很简单,没有任何问题。但是通过reflection,碰到了郁闷的事情。虽然最终搞定了,但是要看过InvokeMember的代码才能知道为什么。

代码:

        Object panel = null;
        Type tpanel = null;

        private void ReflectionForm_Load(object sender, EventArgs e)
        {
            Assembly asm = Assembly.LoadFile(@"D:\test\WindowsApplication5\MyLibrary\bin\Debug\mylibrary.dll");
            tpanel = asm.GetType("MyLibrary.MyPanel");
            panel = tpanel.InvokeMember("", BindingFlags.CreateInstance, null, this, new object[] { });

            (panel as Control).Location = new Point(100, 100);
            this.Controls.Add(panel as Control);                          
        }

出错的地方:

tpanel.InvokeMember("AddBall", BindingFlags.Instance | BindingFlags.Public|BindingFlags.InvokeMethod,
                null, panel, new object[] { obj });

如果上面的panel是object类型,那么一切正常。如果是Control类型,即,我声明为:Control panel,然后
object obj = tpanel.InvokeMember("", BindingFlags.CreateInstance, null, this, new object[] { });
panel = object as Control;
panel.Location = new Point(100,100);
this.Controls.Add(panel);

那么上面的InvokeMember就会提示我:Method not found!如果我修改BindingFlags,去掉Method,那么没有MethodNotFound的exception,但是会有“请使用InvokeMethod,Set/Get Field之类的提示”。如果加上,就是MethodNotFound,真是faint!

google了一些,貌似有些人碰到过,一个post上面说,arguments的类型必须要和method的类型完全一致才可以。我想,如果Panel当作control来用,那么control -> Ball类型会无法转换的,而object -> Ball是允许的。

哪位高手帮忙解释一下?谢谢!

posted on 2007-08-08 21:20:00 by juqiang  评论(10) 阅读(6282)

 
2007年06月22日

(不好意思,置顶几天)

微软招聘SQL专家,如果您认为在下述方面有专长,请积极报名:  

1、工作地点:上海;
2、很强的微软技术背景和产品熟悉度;
3、很强的客户沟通能力;
4、熟悉 SQLServer,熟悉Reporting Service开发、维护
5、熟悉 BI理念、产品,如果有 SharePoint Portal Server 2003 或 Office SharePoint Server 2007 实战经验将优先考虑;

请把个人简历发给我:juqiang1975@msn.com

posted on 2007-06-22 17:03:00 by juqiang  评论(8) 阅读(8741)

 
2007年02月02日

12月份的那个sqlserver死锁分析(连接在下面),我从blogs.msdn上翻译过来的。不过很郁闷,我没有加“转载”或者“翻译”,被一眼尖的哥们儿发现了。

本来想修改原文,增加上“翻译”,但是现在进不去(貌似edit posts很慢),还是放一个单独的post说明此事吧!

郁闷啊!老天可以保证,偶的文品应该没问题的…………

不过,还是应该严重鄙视自己一下。

那个评论在这里:

 http://blog.joycode.com/juqiang/archive/2006/12/18/89218.aspx#93122

 

(我处理的不当,导致了一些争吵,实在对不起大家!此文我从首页撤出,vc和czb兄弟的评论我适当删除。两个当事人我都了解一些,偶异常sorry!!!同时,此文不允许再评论,请后来看得人见谅!

总之一句话,此事我错了,即使想帮我辩解的人,也不要做。错就是错,对就是对,这个blog毕竟给所有人看的。偶极力欢迎大家继续对我的posts批评指正,大家一起提高。3x!)

posted on 2007-02-02 17:02:00 by juqiang  评论(4) 阅读(7966)

 
2007年01月26日

C#代码如下:

private void button2_Click(object sender, System.EventArgs e)
{
        System.Threading.Thread thread = new System.Threading.Thread( new                       System.Threading.ThreadStart(this.ShowOCX2));
        thread.SetApartmentState(System.Threading.ApartmentState.STA);
        thread.Start();
}

 

private void ShowOCX2()
{
        Form2 form = new Form2();
        form.ShowDialog();
}

Form2中是放入了一个ocx(delphi写的一个空ocx,任何自己的代码都没有)。

现在症状是,点击button2之后,出现了form2,直接关闭form2;再次点击button2,程序立刻crash掉。

抓dump(启用了pageheap),发现下面代码:(注意红色的)

Thread 4
Current frame: OCX2Proj1+0x3402
ChildEBP RetAddr Caller,Callee
0513f808 7c9237bf ntdll!ExecuteHandler2+0x26
0513f82c 7c92378b ntdll!ExecuteHandler+0x24, calling ntdll!ExecuteHandler2
0513f850 7c957860 ntdll!RtlDispatchException+0xb1, calling ntdll!RtlpExecuteHandlerForException
0513f874 060e3407 OCX2Proj1+0x3407
0513f880 7c9211a7 ntdll!LdrpCallInitRoutine+0x14
0513f884 7c93e6f4 ntdll!LdrUnloadDll+0x41c, calling ntdll!LdrpCallInitRoutine
0513f888 7c80abf7 kernel32!FreeLibrary+0x3f, calling kernel32!LdrUnloadDll
0513f890 769c3456 ole32!CClassCache::CFinishComposite::Finish+0x1d
0513f894 7c988c37 ntdll!RtlpDphNeedToTrimDelayedFreeQueue+0x3f, calling ntdll!RtlLeaveCriticalSection
0513f8a8 7c98acda ntdll!RtlpDphNormalHeapFree+0xb1, calling ntdll!RtlpDphNeedToTrimDelayedFreeQueue
0513f8bc 7c98770b ntdll!RtlpDebugPageHeapLeaveCritSect+0x10, calling ntdll!RtlLeaveCriticalSection
0513f8c8 7c98af7d ntdll!RtlpDebugPageHeapFree+0x192, calling ntdll!RtlpDebugPageHeapLeaveCritSect
0513f8dc 7c92eafa ntdll!KiUserExceptionDispatcher+0xe, calling ntdll!RtlDispatchException
0513fbdc 060e3402 OCX2Proj1+0x3402 ====> Exception cxr@513f910
0513fb00 7c9306eb ntdll!RtlAllocateHeap+0xeac, calling ntdll!_SEH_epilog
0513fc70 7c9211a7 ntdll!LdrpCallInitRoutine+0x14
0513fc90 7c93e6f4 ntdll!LdrUnloadDll+0x41c, calling ntdll!LdrpCallInitRoutine
0513fcf4 7c939213 ntdll!LdrShutdownThread+0xd7, calling ntdll!LdrpCallInitRoutine
0513fcf8 7c80c096 kernel32!ExitThread+0x3e, calling kernel32!LdrShutdownThread
0513fd70 77d193e9 user32!NtUserPeekMessage+0xc

然后切换进去
0:004> .cxr 513f910
eax=f0f0f0f0 ebx=0000000f ecx=00000000 edx=f0f0f001 esi=06146590 edi=06149638
eip=060e3402 esp=0513fbdc ebp=0513fbf0 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010286
OCX2Proj1+0x3402:
060e3402 8b08 mov ecx,dword ptr [eax] ds:0023:f0f0f0f0=????????
0:004> kb
*** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
0513fbf0 060e3f10 0513fc64 060e3cff 0513fc10 OCX2Proj1+0x3402
0513fc10 060e4246 0513fc70 0513fc60 06149664 OCX2Proj1+0x3f10
0513fc70 7c9211a7 060e0000 00000000 00000000 OCX2Proj1+0x4246
0513fc90 7c93e6f4 061467b0 060e0000 00000000 ntdll!LdrpCallInitRoutine+0x14
0513fd88 7c80abf7 060e0000 0513fdd0 0513fdf4 ntdll!LdrUnloadDll+0x41c
0513fd9c 769c3442 060e0000 0513fe18 769c3456 kernel32!FreeLibrary+0x3f
0513fda8 769c3456 0513fddc 76ab67e0 00000000 ole32!CClassCache::CDllPathEntry::CFinishObject::Finish+0x2f
0513fdbc 769b2557 76991ab0 00000000 00000000 ole32!CClassCache::CFinishComposite::Finish+0x1d
0513fe18 769b2243 0133d878 00000080 00000001 ole32!CClassCache::CleanUpDllsForApartment+0x1b5
0513fe44 769b2171 00000000 0513fe90 76ab67e8 ole32!FinishShutdown+0xcd
0513fe60 769af221 00000000 00000000 0133d878 ole32!ApartmentUninitialize+0x7e
0513fe78 769aee88 0513fe90 00000000 00000001 ole32!wCoUninitialize+0x41
0513fe94 769c31f0 76990000 769ad1a2 0513fec4 ole32!CoUninitialize+0x5b
0513fe9c 769ad1a2 0513fec4 769ad141 76990000 ole32!DoThreadSpecificCleanup+0x47
0513fea4 769ad141 76990000 00000003 00000000 ole32!ThreadNotification+0x37
0513fec4 769ad0e9 76990000 00000003 00000000 ole32!DllMain+0x147
0513fee4 7c9211a7 76990000 00000003 00000000 ole32!_DllMainCRTStartup+0x52
0513ff04 7c939213 769ad0a1 76990000 00000003 ntdll!LdrpCallInitRoutine+0x14
0513ff7c 7c80c096 00000000 00000000 01321d40 ntdll!LdrShutdownThread+0xd7
0513ffb4 7c80b688 00000000 00000000 00000000 kernel32!ExitThread+0x3e

最上面三行就是delphi写的那个空白ocx,下面三个是各自的代码:
0:004> u OCX2Proj1+0x4246
OCX2Proj1+0x4246:
060e4246 ?? ???
^ Memory access error in 'u OCX2Proj1+0x4246'


0:004> u OCX2Proj1+0x3f10
OCX2Proj1+0x3f10:
060e3f10 ?? ???
^ Memory access error in 'u OCX2Proj1+0x3f10'

0:004> u OCX2Proj1+0x3402
OCX2Proj1+0x3402:
060e3402 8b08 mov ecx,dword ptr [eax]
060e3404 ff51fc call dword ptr [ecx-4]
060e3407 c3 ret
060e3408 53 push ebx
060e3409 56 push esi
060e340a 57 push edi
060e340b 89c3 mov ebx,eax
060e340d 89d7 mov edi,edx

 

0:004> d [ecx-4]
fffffffc ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
0000000c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
0000001c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
0000002c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
0000003c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
0000004c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
0000005c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
0000006c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????

 

郁闷之极,好像用VB写一个空OCX放到form上,然后thread调用起来,也有这个问题。哪位老大见过这个问题?貌似和Appartment有关?

posted on 2007-01-26 14:32:00 by juqiang  评论(8) 阅读(8720)

 
2007年01月19日

第一步,在业务高峰期抓取样本数据(2个小时左右)。采用的工具是sqlserver自带的profiler,也叫事件探查器,如下图:

进入后,点击最左面的按钮,建立一个新的跟踪:

登录需要用DBO权限,所以可以用sa登录,也可以用windows集成验证方式(如果当前登录的就是sqlserver的话)

新建跟踪,一共有4个tab页进行配置,首先看第一个。跟踪名称不用更改,默认的即可。保存一共有两种方式,一是文件,扩展名是.trc(这种方式方便你把客户那里的跟踪结果发给你),其二是数据库中的表。

为了分析方便,我们把它另存为表。此时sql提示你重新进行登录,这里我们把表保存到master中

假设表名字叫做jq(如果有重复的,系统会提示是否覆盖)

确定后回到了刚才的第一个tab页中:

然后切换到第二个选项卡中:

左面列出了各种事件类(Event Class),右面是当前已有的事件类。对于性能调优,我们不需要安全审核、会话信息,点击删除按钮即可:

继续切换到第三个tab页上,这里的数据列默认就够了,当然,如果你看着不顺眼,可以把Appname/NT username等都删除。

最后一个tab页上,我们需要把系统自己产生的事件ID屏蔽掉:

把那个排除系统ID进行check即可,如下图:

所有项目配置好后,点击“运行”按钮。持续运行两个小时左右即可(业务高峰期,能典型的反应客户最近一段时间内的业务模式)

好了,第一步的准备工作完成了,等待一段时间后,我们开始检查刚才自动保存到master中的表jq。

第二步,开始查找影响速度的地方。

打开查询分析器(sql analyzer),登录到master中,从 表jq里面按照I/O倒序,读取若干个sql。根据我的习惯,一般是读取1000条记录。为什么根据I/O来找呢,而不是根据时间来找呢?原因很简单,一句SQL执行,“稳定”的是I/O,而duration是一个不稳定的因素。我们进行sql调优的目的,就是降低I/O成本,从而提高效率。(一般而言,I/O降低了,duration自然就会降低)详细内容,参考我以前的post:http://blog.joycode.com/juqiang

执行完成后,我们仔细看下面的输出。

1、 XL_TALLY_Proc04这个sp的reads最大,将近100w,duration也达到了25秒多。

2、 Erp_IM_GMBill_GetBill这个sp的I/O不算大,才7w,duration平均都在1秒多点。但是这个sp执行的次数非常多。

经过询问客户,XL_TALLY_Proc04这个sp执行的频度很低,一天也就一两次,但是Erp_IM_GMBill_GetBill大概5分钟就要一次。这样整体I/O就占用的非常大。

所以这里我们要重点分析Erp_IM_GMBill_GetBill这个sp,而不是第一个!

总结一个原则就是:调整的重点是客户最关心的内容,是执行频度最高、看起来I/O又比较大的那种。I/O最大的,不一定是我们要优先解决的内容。

第三步,开始分析刚才看到的那个语句。既然我们要分析I/O,那么就要把I/O打开,这样每次调整sql,我们都能随时看到I/O的变化情况。这句很有用处地:set statistics io on

单纯看I/O变化,我们会晕倒的。因为我们不知道自己做的任何改动,对I/O是如何产生影响的。所以,还要看sql的执行计划是怎佯的。 在查询分析器中,我们按Ctrl+K,或者如下图的菜单,check上即可。

好了,准备工作都做好了,下面开始干活了。

我们首先看sql语句的调优,假设下面这条sql语句性能低下:

上面的sql一共读取了6636条数据,逻辑读是1126。那么这个I/O是否合理呢?大了还是小了?还有改进的余地吗?我们看执行计划:

哦,一共4个咚咚在里面。Index seek的成本占了2%, index scan的占了47%,hash match占了51%,select最终是0%。我们应该牢记第二个原则,所有的index,尽可能的都走index seek。

我们看一下billsoflading的索引信息:

当前索引为什么走scan,这里就不说了,感兴趣的可以随便找一本介绍数据库索引的书籍来看看即可。根据我以前那篇blog的描述,我们知道应该建立一个复合索引(也叫convered index):boldate+companyid+bolcode

然后我们重新执行sql,看看I/O变化情况:

Ooh,非常cool!logical reads降低到了50。为什么会这样呢?我们看一下执行计划:

原来是index scan变成了index seek,效率自然大大的提升!

Sql语句在index上调优的方法,基本就是这样。我们继续看sp的。

对于sp的调优,有一点是和sql调优不同的:sp内部的逻辑处理可能非常复杂。单纯从查询分析器中,我们无法得知哪一小块的sql执行的I/O最大,我们只能看到一个总体的描述。所以,我们要知道sp内部的信息。

首先,了解自己当前的spid是多少。一种方法是select @@spid,另一种方法是看查询分析器下面的status bar的信息。

Ooh,我的spid是101。(上图的最下面那个tips)

然后我重新打开profiler(事件探查器),重新建立一个跟踪,这里面要修改第二个tab页的信息,把左面事件列“存储过程”中的SmtpCompleted加上

增加后的样子如下:

然后修改第4个tab页,把刚才看到的spid=101的信息填上:

点击运行后,这样profiler只能抓到在查询分析器中,spid=101那个窗口发送的sql。我们切换回查询分析器,执行有问题的sp,执行完成后,我们再回到profiler,点停止按钮。一个sp内部所有执行的sql,都被分开了!

这次的结果假设保存在了jq2表中,我们把所有执行的小片sql都列出来:

第一个是sp执行后的总体结果,I/O为62328,就是这个sp自己的。第二个是向临时表中插入数据,I/O为61514,我们很容易看到,这一句占用了整个sp的大概95%以上的成本。如果我们把这句insert into #temptable搞定,整个sp的成本自然就下来了。所以我们需要把这句insert搞出来。

但是慢着!default情况下,sqlserver的results只显示很少的字符,第二行的sql,我们根本抓不全的,所以我们需要修改一下设置。在查询分析器的工具-选项菜单中,切换到“结果”这个tab页,修改每列最多字符个数为8192(这是最大的允许值),然后点击“确定”按钮,重新从jq2中读取信息。也许你会问,如果某个sql特别长,怎么办?其实很简单,在你的代码中把这句sql单独写到log中,或者直接修改sp,把这句print出来即可。

Ok,我们把这句insert sql抓下来后,放到查询分析器中。因为temptable我们没有它的结构,所以我们把insert部分注释掉,看后面的select语句。执行后,ooh,在goodsmovement表上的成本是57834。

老办法,我们继续看执行计划:

其实,现在又回归到了sql调优的步骤,下面的工作我就不写啦!

这个步骤,看起来很简单,希望大家对于sql调优(索引部分)心中都有这么一个概念,知道第一步作什么,第二步作什么。还是那句话,索引调优,基本上是最简单的。但是貌似简单的东西,我们越应该重视。你随便找一个应用跟踪一下,各种效率低下的索引,会让你实在#¥*#(**……¥

最后,看看偶的桌面,14集何时能够出来呢?期待中……

posted on 2007-01-19 13:06:00 by juqiang  评论(40) 阅读(16042)

 
2007年01月11日

这个回复是针对我上一个post大家的热情回复而写的。安全问题容易被人忽视,倒不是说不重视,而是自己苦心研究出来的东西,很容易从其他人另一个角度被攻破。

尤其在.NET上,这种问题之所以出现,我认为是大家对于.NET的视角不同造成的。.net让快速开发确实达到了,但是也让程序员丧失了“警惕性”。

那位兄弟说的破解成本确实是问题。成本太高,破解没有意义了。但是致命的是,我接触到的这些国内ERP厂商,对于序列号控制这部分,实在是太脆弱了,脆弱到破解成本几乎就是0 - 虽然后面有大型应用、技术支持这个说法。

我印象中看卡巴斯基的书上说,最好的“提高破解难度”的方法是在native code中进行SEH处理,把核心代码放到catch里面。据说这样会让cracker晕倒………………

至于加壳与否,我认为只要想做,有足够的耐心,把内存都dump出来,象dos下面破解,一样的。

posted on 2007-01-11 11:38:00 by juqiang  评论(15) 阅读(7005)

 
2007年01月10日

业界对于Java/.NET程序的一个批评就是其安全性。由于IL的特点,各种reflector很容易把代码搞出来。混淆器,貌似一个很常用的功能吧?今天看某个软件,与我们的应用有些类似,所以想借鉴一下。安装好之后,发现有一个License Manager,两个按钮,一个是生成申请信息,一个是导入序列号。(通用的做法,我们的应用也是这么做的)
用reflector打开后,ooh,大概80%的信息都被混淆掉了。field/method的名称,都成了稀奇古怪的文字。还有,我的File Disassembler plug-in居然不能用了,于是只能把所有的method/field信息都paste到notepad上面。(下面所有代码,出于众所周知的原因,我又做了第二次“混淆”,所有的变量/方法名称,都是随意写的)

映入眼帘的第一个method是.ctor,我们知道这个是缺省的构造方法。看代码,如下:

public SomeConstructor()
{
      this.Abcdef123456();
}

上面的方法就是被混淆过了,虽然没意义,但是我们能够“猜测”出来!
进入上面的Abcdef123456方法,如下:

private void Abcdef123456()
{
      this.fedcba654321 = new Container();
      Button button1 = new Button();
      Button button2 = new Button();
      ...
}

好熟悉吧!这就是InitializaComponent方法,查找所有的Abcdef123456,统一替换为这个新名字就可以了。同样的道理,通过看程序运行时候的界面、在查找代码,能更正90%以上的混淆名称,变成一个清晰的表达(虽然不一定和源代码相同)

继续看reflector的结果,有两个分别是Button1_Click和Button2_Click方法。
先看第一个,里面有段代码如下:
byte[] buffer1 = MD5.Encrypt(this.B33498573V + '\v' + this.J39475SDf, false);
stream1.Write(buffer1, 0, buffer1.Length);
byte[] buffer2 = MD5.Encrypt(new UnicodeEncoding().GetBytes(text1));
stream1.Flush();
if (SomeCondition == true)
{
      SaveCPUandDISKInfo(this.B33498573V.Text , this.J39475SDf.Text);
}
InfoShow("\u751f\u6210");

看起来似乎很麻烦?晕,居然还有MD5,还有一些复杂的逻辑判断。怎么搞?分析那个MD5怎么加密的吗?没意义!
首先我们看最后面的InfoShow,我们都知道,这是Unicode编码方式下的文字,简单的用Console.WriteLine出来即可,上面的两个字是“生成”,于是我们猜测,这个和序列号可能相关。很自然的,我们会想到,对于注册的话,会有“成功”或者“失败”。前者转换为unicode是:\u6210\u529f。好吧,我们搜索“\u6210\u529f”。幸运!我们在Button2_Click方法中找到了这个字符串。这个方法代码如下:
if (MD5.CheckRegisterKey(this.FileWillImported.Text)){
    File.Copy();
    ReadRegistry();
    GetCPUandDISKinfoFromDatabase();
    if(ResultFromUpline() == true){
        SetCPUandDISKinfoIntoDatabase();
    }
    InfoShow("\u6210\u529f");
}
else{
    InfoShow("\u975e\u6cd5");
}

我们看到上面的代码,进行了一些复杂的操作,包括什么MD5/SHA/RSA等,最后成功才显示出来一个"\u6210\u529f"。作为游戏,我们继续寻找“失败”的unicode bytes,ooh,没有找到!但是从上面的代码我们可以看出,最后的else是有问题的。同样的Console.WriteLine一下,显示出来的是“非法”这两个汉字。

下面的工作就简单了,ildasm /out方式,把IL弄出来,直接调用SetCPUandDISKinfoIntoDatabase方法,屏蔽掉所有的判断即可。
再用reflector看,该文件并没有任何对于非托管代码的refrence,安装目录下面也没有任何非托管dll,所以基本确定就是这样子了。

总结一下,我们用到的方法,都是20年前的方法。找到关键字符串,查找上下文可疑代码,把相关的判断信息都屏蔽掉,工作就完成了。那些花哨的各种“非对称算法”,没有任何意义。因为在加密解密工作中,流程的转向是重要的,流程本身,是无意义的。这里没有我们10年前看到的那些让人目眩的反侦查手法。

前段时间帮一朋友看过一个.net的应用,用usb key来做的,调用了自己写的Win32 API。同样的弊病,写了非常复杂的加密、解密、反跟踪、反调试处理,可惜的是,这一切都是在Win32中作的,.NET代码只是简单的做了CALL。屏蔽掉了200多行的IL,直接return true,唔,这个世界清净了………………

(再次声明,本文纯属对于.NET简单混淆的一个讨论,文中对于该软件的任何可以识别的特征,都进行了“混淆”)
(对于破解和反汇编,可以看卡巴斯基本人写的那本黑客反汇编揭秘,本人对于汇编纯属文盲)

反过头来,我们考虑本文标题的内容,.NET或者J2EE代码应该如何保护?混淆是必要的,但太脆弱。反跟踪、反调试,我记着有高手在作,但是我不认为这能解决根本问题。不管多复杂的加密手段,被一些低级的代码调用的时候,总能很快的被搞定,如上面分析的那些代码。

 

posted on 2007-01-10 14:57:00 by juqiang  评论(11) 阅读(6942)

 
2007年01月04日

很好的日子,2007年1月1日收到了确认通知。这是偶第二次通过Solution Architect的MVP,很是高兴。

posted on 2007-01-04 10:37:00 by juqiang  评论(11) 阅读(7623)

 
2006年12月29日

貌似PageParser的大量调用,会导致大量的小size的dll、众多memory hole的出现。得到eparg的指教,越来越感觉是这个样子。据joycode老大说,只有2.0这个样子,只有特定的这一个app这个样子。

准备写一个.net 2的测试程序,看这个hole是不是确实很明显?

(详情等下文)

posted on 2006-12-29 01:03:00 by juqiang  评论(5) 阅读(7196)

 
2006年12月22日

Session,Session,Session!(请耐心阅读………………)

地球人都知道,asp.net中有三种方式存放我们的session objects。In Proc模式,在cache中存放对象。StateServer在State Service中存放,最后一种是存放在SQL Server中。对于In Proc模式,太多的session对象,意味着高内存占用;对于后两者,意味着序列化和反序列化的性能损失。
Session存放的东西太多,不一定意味着性能的问题,但这依赖于你往session里面存放的东西。
让我们假设一个场景,你开发了一个网上商店,有很少的人在用,所以你只用了一台web server,使用了In Proc模式,你的程序对每个用户的订单,都存放了一些dataset。
突然,你的程序出名了!一个大公司来找你,于是,你增加了N台web server,同时把Session State也修改成了SQL Server模式。

问题来了!
asp.net进程,内存占用很好(800MB – 1GB),有时候会提示OutOfMemory异常,或者提示.net进程被recycled了。dump的大小为1,473,913 bytes,所以,大概就是1.4G。首先,我们看看托管堆上面都有些啥东西?

0:023> !eeheap -gc
Number of GC Heaps: 2
------------------------------
Heap 0 (0x000b7198)
generation 0 starts at 0x022104d4
generation 1 starts at 0x022037c0
generation 2 starts at 0x02170030
ephemeral segment allocation context: none
 segment    begin       allocated     size
0x2170000 0x2170030  0x224a4e0 0xda4b0(894,128)
Large object heap starts at 0x0a170030
 segment    begin       allocated     size
0x0a170000 0x0a170030  0x0acf0b20 0x00b80af0(12,061,424)
0x0d490000 0x0d490030  0x0e3d2450 0x00f42420(16,000,032)
0x12010000 0x12010030  0x12f52460 0x00f42430(16,000,048)
0x13010000 0x13010030  0x13f52460 0x00f42430(16,000,048)
0x15010000 0x15010030  0x15f52460 0x00f42430(16,000,048)
0x1a010000 0x1a010030  0x1af52460 0x00f42430(16,000,048)

0x71ca0000 0x71ca0030  0x72be2470 0x00f42440(16,000,064)
0x748b0000 0x748b0030  0x757f2470 0x00f42440(16,000,064)
0x7d0e0000 0x7d0e0030  0x7d881250 0x007a1220(8,000,032)
Heap Size  0x2d5b4e10(760,958,480)
------------------------------
Heap 1 (0x000ede88)
generation 0 starts at 0x06249b58
generation 1 starts at 0x0623e190
generation 2 starts at 0x06170030
ephemeral segment allocation context: none
 segment    begin       allocated     size
0x6170000 0x6170030  0x6283b64 0x113b34(1,129,268)
Large object heap starts at 0x0b170030
 segment    begin       allocated     size
0x0b170000 0x0b170030  0x0b9f1c90 0x00881c60(8,920,160)
0x0c3e0000 0x0c3e0030  0x0d322460 0x00f42430(16,000,048)
0x0e490000 0x0e490030  0x0f3d2460 0x00f42430(16,000,048)
0x11010000 0x11010030  0x11f52460 0x00f42430(16,000,048)
0x14010000 0x14010030  0x14f52450 0x00f42420(16,000,032)
0x16010000 0x16010030  0x16f52480 0x00f42450(16,000,080)
0x17010000 0x17010030  0x17b81b60 0x00b71b30(12,000,048)
0x18010000 0x18010030  0x18b81b60 0x00b71b30(12,000,048)
0x19010000 0x19010030  0x19f52460 0x00f42430(16,000,048)
0x1b010000 0x1b010030  0x1bf52460 0x00f42430(16,000,048)

0x61010000 0x61010030  0x61f52470 0x00f42440(16,000,064)
0x62db0000 0x62db0030  0x63cf2470 0x00f42440(16,000,064)
0x657e0000 0x657e0030  0x66722470 0x00f42440(16,000,064)
0x685c0000 0x685c0030  0x69502470 0x00f42440(16,000,064)
0x6e110000 0x6e110030  0x6ec81b70 0x00b71b40(12,000,064)
0x72ca0000 0x72ca0030  0x73be2470 0x00f42440(16,000,064)
0x77ff0000 0x77ff0030  0x78f32470 0x00f42440(16,000,064)
0x7e0e0000 0x7e0e0030  0x7f022470 0x00f42440(16,000,064)
Heap Size  0x286a4124(678,052,132)
------------------------------
GC Heap Size  0x55c58f34(1,439,010,612)

!eeheap –gc告诉我们两个事情:
a) the GC Heap大小在1.4G左右,和上面的总内存占用非常接近。这说明大部分内存占用,都在托管堆上。
b) 大部分内存占用都在LargeObjects上面(>85000bytes的东西)

很自然的,我们要看看在heap里面的LO到底都是啥东西?
0:023> !dumpheap -min 85000 -stat
Using our cache to search the heap.
Statistics:
        MT      Count     TotalSize Class Name
0x000eda20          1       920,144      Free
0x01b2209c         33   132,000,528 System.Object[]
0x01b226b0        163 1,304,001,956 System.Int32[]
Total 197 objects, Total size: 1,436,922,628
大部分内存被Int32数组占用了,还有132M被一个对象数组占用了。注意啊!这里的132M和那个1.3G,都只是object本身的大小,而不包括存储在Object数组里面的对象的大小。如果要看详细情况,我们需要跑一下!objsize才可以。
0:023> !dumpheap -min 85000
Using our cache to search the heap.
   Address         MT     Size  Gen
0x0b170030 0x000eda20  920,144   -1      Free
0x0a920210 0x01b2209c 4,000,016   -1 System.Object[]
0x247b1250 0x01b2209c 4,000,016   -1 System.Object[]
0x277b1250 0x01b2209c 4,000,016   -1 System.Object[]
0x287b1250 0x01b2209c 4,000,016   -1 System.Object[]

0x51af0030 0x01b226b0 8,000,012   -1 System.Int32[]
0x52291250 0x01b226b0 8,000,012   -1 System.Int32[]
0x53b40950 0x01b226b0 8,000,012   -1 System.Int32[]
0x56620030 0x01b226b0 8,000,012   -1 System.Int32[]
0x57620030 0x01b226b0 8,000,012   -1 System.Int32[]
0x5a440030 0x01b226b0 8,000,012   -1 System.Int32[]
0x5abe1250 0x01b226b0 8,000,012   -1 System.Int32[]
0x5c660030 0x01b226b0 8,000,012   -1 System.Int32[]
0x5ce01250 0x01b226b0 8,000,012   -1 System.Int32[]
0x61010030 0x01b226b0 8,000,012   -1 System.Int32[]
0x617b1250 0x01b226b0 8,000,012   -1 System.Int32[]
0x62db0030 0x01b226b0 8,000,012   -1 System.Int32[]
0x63551250 0x01b226b0 8,000,012   -1 System.Int32[]
0x657e0030 0x01b226b0 8,000,012   -1 System.Int32[]
0x65f81250 0x01b226b0 8,000,012   -1 System.Int32[]
注意Gen这一列,这些object都没有被GC,为啥?随便找一个,看看吧!
0:023> !gcroot 0x72ca0030
Scan Thread 16 (0xbd8)
ESP:1a3f5e0:Root:0x6280d38(System.Web.HttpContext)->
0x62809ec(System.Web.Hosting.ISAPIWorkerRequestInProcForIIS6)->
0x61b7030(System.Web.HttpWorkerRequest/EndOfSendNotification)->
0x61b47d4(System.Web.HttpRuntime)->
0x61b4ca0(System.Web.Caching.CacheMultiple)->0x61b4cc4(System.Object[])->
0x61b4cdc(System.Web.Caching.CacheSingle)->
0x61b4dac(System.Web.Caching.CacheExpires)->0x61b4ff8(System.Object[])->
0x61b5ab8(System.Web.Caching.ExpiresBucket)->
0x222e63c(System.Web.Caching.ExpiresEntry[])->
0x220292c(System.Web.Caching.CacheEntry)->
0x22028fc(System.Web.SessionState.InProcSessionState)->
0x2202690(System.Web.SessionState.SessionDictionary)->
0x220273c(System.Collections.Hashtable)->
0x2202770(System.Collections.Hashtable/bucket[])->
0x2202810(System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)->
0x72ca0030(System.Int32[])
Scan Thread 20 (0x89c)
Scan Thread 22 (0xa5c)
Scan HandleTable 0xdc9d8
Scan HandleTable 0xea6e8
Scan HandleTable 0x1531e8
看这条链,我们发现Int32被Cache root了(看GC的原理就知道这句话啥意思了),看上面的InProcSessionState,我们知道现在是InProc模式。
继续看……
0:023> !dumpaspnetcache -stat
Going to dump the ASP.NET Cache.
        MT      Count     TotalSize Class Name
0x0211cc9c          1            20 System.Web.Security.FileSecurityDescriptorWrapper
0x020c242c          2            56 System.Web.UI.ParserCacheItem
0x0206c66c          5           260 System.Web.Configuration.HttpConfigurationRecord
0x0c2e7014          1           316 System.Web.Mobile.MobileCapabilities
0x79b94638          4           376 System.String
0x0c2eaeb4        151         7,248 System.Web.SessionState.InProcSessionState
Total 164 objects, Total size: 8,276
好,我们找到了151个Session对象。
0:023> .foreach (obj {!dumpheap -mt 0x0c2eaeb4 -short}){!objsize ${obj}}
sizeof(0x22028fc) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2202a10) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2202cfc) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2202fe8) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x22032d4) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x22035c0) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2203a38) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2203d24) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2204010) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)

sizeof(0x6248080) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6248334) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x62485e8) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x624a84c) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x624d7b8) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6250724) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6253690) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x62565fc) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6259568) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x625c4d4) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x625f440) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x62623ac) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6265318) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6268284) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x626b1b8) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
看到了!151个session对象,每个拿到了8-12M左右的数据。就是这个鸟玩意搞的内存有问题啦!
随便找一个,看看什么在里面???
0:023> !do 0x626b1b8
Name: System.Web.SessionState.InProcSessionState
MethodTable 0x0c2eaeb4
EEClass 0x0c1c5660
Size 48(0x30) bytes
GC Generation: 0
mdToken: 0x02000132  (c:\windows\assembly\gac\system.web\1.0.5000.0__b03f5f7f11d50a3a\system.web.dll)
FieldDesc*: 0x0c2eae0c
        MT      Field     Offset                 Type       Attr      Value Name
0x0c2eaeb4 0x40009f2      0x4                CLASS   instance 0x06269f74 dict
0x0c2eaeb4 0x40009f3      0x8                CLASS   instance 0x00000000 staticObjects
0x0c2eaeb4 0x40009f4      0xc         System.Int32   instance 20 timeout
0x0c2eaeb4 0x40009f5     0x18       System.Boolean   instance 0 isCookieless
0x0c2eaeb4 0x40009f6     0x10         System.Int32   instance 0 streamLength
0x0c2eaeb4 0x40009f7     0x19       System.Boolean   instance 0 locked
0x0c2eaeb4 0x40009f8     0x1c            VALUETYPE   instance start at 0x0626b1d4 utcLockDate
0x0c2eaeb4 0x40009f9     0x14         System.Int32   instance 1 lockCookie
0x0c2eaeb4 0x40009fa     0x24            VALUETYPE   instance start at 0x0626b1dc spinLock
每个InProcSessionState对象都有一个叫做_entriesArray的dict成员,装有实际的session对象列表。就是上面偏移为0x04的那行。
0:023> !do 0x06269f74
Name: System.Web.SessionState.SessionDictionary
MethodTable 0x0c2e0c54
EEClass 0x0c1c1308
Size 44(0x2c) bytes
GC Generation: 0
mdToken: 0x0200013b  (c:\windows\assembly\gac\system.web\1.0.5000.0__b03f5f7f11d50a3a\system.web.dll)
FieldDesc*: 0x0c2e0b30
        MT      Field     Offset                 Type       Attr      Value Name
0x0206b338 0x4000a8b     0x24       System.Boolean   instance 0 _readOnly
0x0206b338 0x4000a8c      0x4                CLASS   instance 0x06269fb8 _entriesArray
0x0206b338 0x4000a8d      0x8                CLASS   instance 0x06269fa0 _hashProvider
0x0206b338 0x4000a8e      0xc                CLASS   instance 0x06269fac _comparer
0x0206b338 0x4000a8f     0x10                CLASS   instance 0x0626a020 _entriesTable
0x0206b338 0x4000a90     0x14                CLASS   instance 0x00000000 _nullKeyEntry
0x0206b338 0x4000a91     0x18                CLASS   instance 0x00000000 _keys
0x0206b338 0x4000a92     0x1c                CLASS   instance 0x00000000 _serializationInfo
0x0206b338 0x4000a93     0x20         System.Int32   instance 4 _version
0x0c2e0c54 0x4000a0f     0x25       System.Boolean   instance 1 _dirty
0x0c2e0c54 0x4000a0e        0                CLASS     shared   static s_immutableTypes
    >> Domain:Value 0x000dad08:NotInit  0x00104f30:0x021be0dc <<
同样,还是看_entriesArray,偏移为0x04的那行
0:023> !do 0x06269fb8
Name: System.Collections.ArrayList
MethodTable 0x79ba2ee4
EEClass 0x79ba3020
Size 24(0x18) bytes
GC Generation: 0
mdToken: 0x02000100  (c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)
FieldDesc*: 0x79ba3084
        MT      Field     Offset                 Type       Attr      Value Name
0x79ba2ee4 0x4000362      0x4                CLASS   instance 0x06269fd0 _items
0x79ba2ee4 0x4000363      0xc         System.Int32   instance 3 _size
0x79ba2ee4 0x4000364     0x10         System.Int32   instance 3 _version
0x79ba2ee4 0x4000365      0x8                CLASS   instance 0x00000000 _syncRoot
哦,Name是ArrayList,好,我们继续看0x04对应的那个_items吧!
0:023> !do -v 0x06269fd0
Name: System.Object[]
MethodTable 0x01b2209c
EEClass 0x01b22018
Size 80(0x50) bytes
GC Generation: 0
Array: Rank 1, Type CLASS
Element Type: System.Object
Content: 16 items
------ Will only dump out valid managed objects ----
   Address         MT Class Name
0x0626a81c 0x0206b784 System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry
0x0626a82c 0x0206b784 System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry
0x0626a83c 0x0206b784 System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry
----------
对于每个对象,我们打印出0x04位置的Name和0x08位置的Size。
0:023> .foreach (obj {!do -v 0x06269fd0 -short}){.echo ***;!do poi(${obj}+0x4);!do poi(${obj}+0x8);!objsize ${obj}}
***
String: somestring
String: this is a string i stored in session scope
sizeof(0x626a81c) =      160 (    0xa0) bytes (System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)
***
String: alargeintarray
Name: System.Int32[]
MethodTable 0x01b226b0
EEClass 0x01b22638
Size 8000012(0x7a120c) bytes
GC Generation: 3
Array: Rank 1, Type System.Int32
Element Type: System.Int32
Content: 2,000,000 items
sizeof(0x626a82c) = 8,000,076 (0x7a124c) bytes (System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)
***
String: sometimesbig
Name: System.Object[]
MethodTable 0x01b2209c
EEClass 0x01b22018
Size 4000016(0x3d0910) bytes
GC Generation: 3
Array: Rank 1, Type CLASS
Element Type: System.Object
Content: 1,000,000 items
sizeof(0x626a83c) = 4,000,076 (0x3d094c) bytes (System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)
到这里,我们看到,一个很小的字符串:this is a string i stored in session scope。在Session的 somestring里面。然后是在Session的alargeintarray有一个8M的Int32数组,最后,是一个4M的sometimesbig在session的sometimesbig。
下面如何解决?就看我们的业务逻辑和代码实现啦!和偶无关了,嗬嗬!

posted on 2006-12-22 00:23:00 by juqiang  评论(18) 阅读(11624)

 
2006年12月21日

(序 & 跋)

   此文及后面的系列,都是从tess老大那里翻译过来的。一直和GTEC的老牛们作CASE(此句有误,一直提CASE,等老牛们提供答案),算是粘到了一点仙气。偶一直比较懒,所以以前精心抄袭的文章,今天再次精心作序于此,希望对各位挣扎于现实与理想的各位,共享,共勉。tess老大的文章国内似乎有人翻译过,但偶个人观点,不看好,因为好东西都没了哦。

   不是我不舍得,意思是,我的post里面基本上没有link,但是从google上都能搞到,如tess老大的系列debug文章。写程序的人,用好google应该是第一要素啊,哇哈哈哈!(百度除外,偶鄙视的公司)

   每个post的题目都挺吓人,但是最终发现问题以及解决掉的方法,都异常简单。简单,产生丑陋。

 

问题描述:

程序慢的要死,CPU占用始终持续在70%-80%之间

解决步骤:

性能监视器。对于高CPU占用,一般的是这三个原因:

·         高的离谱的循环

·         太多的加载(比如,许多小的对象被频繁的处理)

·         GC作了太多的事情

第一种情况,当你在恰当的时机抓到一个dump,就非常容易解决,一般而言,都是因为业务处理逻辑造成的。第二种情况,一般需要从硬件上考虑,scale up或者scale out,都行。

是否是GC的问题,我们需要看性能监视器里面的.NET CLR Memory计数器。这里面,最重要的是.net CLR Memory / % Time in GC. 这个值的阀值,可能是5%或者30%或者20%。实际上,没有一个准确的阀值存在的。当然,这个数字理论上应该接近于0%才对。

在GC里面,导致高CPU占用的原因,通常是因为过高的分配速率(对应到性能监视器里面的.net CLR Memory / allocated bytes/sec计数器。但实际上,如果所有的GC操作都在第0代上,则不会导致这个问题。真正的元凶,是大量的2代操作。如N多的对象在被移动到2代或者从2代中被释放。另一个原因就是我们熟知的大对象操作(LOH)

再强调一次,没有什么准确的指标,就告诉我们,超过了它就是出问题了,这是不可能的。包括微软给我们的大多数Practics Training,只有“尽量”、“尽可能”、“如果”等,而不是“一定”、“必须”。

如同我在那篇“浅谈GC中”讲到的一样,如果你搞了GC.Collect(2)或者GC.GetTotalMemory(true),那么也会导致大量的2代回收。

对于这个问题,我从性能监视器中抓到了这些数据:

% Time in GC                        ~40 % average
allocated bytes / sec              400 MB average
# Induced GC                        0
# Gen 0 Collections                28.379
# Gen 1 Collections                28.378
# Gen 2 Collections                28.378

看第二行,真是让人晕倒!每秒分配400M字节!但实际上,我的代码中没有分配任何这么大的东西,有点太离谱了吧?!如果看最后面三行,也比较搞笑,0、1、2代的分配几乎完全相同。这实际上说明有LOH在压缩,或者有大量的对象冲进了第2代,然后又被立刻释放掉。

开始debug吧!

GC问题很难debug,因为:

1. 如果在GC中间过程中用adplus -hang模式抓了一个dump,基本上从dump里面看不到任何高CPU占用的原因。
2. 即时你通过性能监视器找到了GC的问题,也抓到了dump,但是很难分析。

实际上,通用的做法是每隔一小段时间,你就抓一次dump。如果看起来都差不多,那么有可能就能分析出哪里的问题了。

步骤1 - 我们在GC里面不?

如果我们的OS是server,那么每个CPU有一个GC线程(如果超线程的话,就是2个)。如果是非server的,那么只有一个GC线程。我们正在看到的这个dump,是在一个双核的、带有.NET2.0的机器上产生的。

如果我们不在GC过程中,那么我们会有两个线程(每个CPU一个):

  18  Id: 134c.918 Suspend: 1 Teb: 7ffa8000 Unfrozen
ChildEBP RetAddr  Args to Child             
01e1fe68 7c822124 77e6baa8 00000398 00000000 ntdll!KiFastSystemCallRet
01e1fe6c 77e6baa8 00000398 00000000 00000000 ntdll!NtWaitForSingleObject+0xc
01e1fedc 79e77fd1 00000398 ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xac
01e1ff20 79e77f9a 00000398 ffffffff 00000000 mscorwks!PEImage::LoadImage+0x199
01e1ff70 79e77f50 ffffffff 00000000 00000000 mscorwks!CLREvent::WaitEx+0x117
01e1ff80 79f3549b ffffffff 00000000 00000000 mscorwks!CLREvent::Wait+0x17
01e1ffa8 79f6ece3 00000000 b28c067c 01e1ffec mscorwks!SVR::gc_heap::gc_thread_function+0x2e
01e1ffb8 77e66063 000e7660 00000000 00000000 mscorwks!SVR::gc_heap::gc_thread_stub+0x9b
01e1ffec 00000000 79f6ec79 000e7660 00000000 kernel32!BaseThreadStart+0x34

上面的代码表明,GC正在等着干活……在我抓到的dump中,GC看起来这个样子:

16  Id: f28.1150 Suspend: 1 Teb: fff82000 Unfrozen
ChildEBP RetAddr  Args to Child             
0248fd28 7d4d8c46 000002dc 00000000 00000000 ntdll!ZwWaitForSingleObject+0x15
0248fd98 79e77fd1 000002dc ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xac
0248fddc 79e77f9a 000002dc ffffffff 00000000 mscorwks!PEImage::LoadImage+0x199
0248fe2c 79e77f50 ffffffff 00000000 00000000 mscorwks!CLREvent::WaitEx+0x117
0248fe3c 79f35e1a ffffffff 00000000 00000000 mscorwks!CLREvent::Wait+0x17
0248fe54 7a0d5a3b 001afbf0 0000000d 00000002 mscorwks!SVR::t_join::join+0x61
0248ff54 79f391bf 00000002 001afbf0 00000000 mscorwks!SVR::gc_heap::plan_phase+0xd78
0248ff70 79f39954 00000002 ffffffff 001afbf0 mscorwks!SVR::gc_heap::gc1+0x57
0248ff88 79f35422 00000000 00000000 001afbf0 mscorwks!SVR::gc_heap::garbage_collect+0x37c
0248ffa8 79f6ece3 00000000 914b1904 0248ffec mscorwks!SVR::gc_heap::gc_thread_function+0x68
0248ffb8 7d4e0729 001afbf0 00000000 00000000 mscorwks!SVR::gc_heap::gc_thread_stub+0x9b
0248ffec 00000000 79f6ec79 001afbf0 00000000 kernel32!BaseThreadStart+0x34

看上面的粗体,哦,正在Collect。就是说,我们闯到了GC的肚子里面。

步骤2 - GC为什么开始工作了?

先看一下,CPU都被谁用掉了?

0:029> !runaway
 User Mode Time
  Thread       Time
 
 14:11b8      0 days 0:00:55.687
  16:1150      0 days 0:00:45.500
  17:7a8       0 days 0:00:43.875
  21:1244      0 days 0:00:23.140
 
  0:ea0       0 days 0:00:00.046
  29:fc8       0 days 0:00:00.000
  28:11f0      0 days 0:00:00.000

0:021> .time
Debug session time: Tue Jun 20 09:46:04.000 2006 (GMT+2)
System Uptime: 4 days 6:36:51.031
Process Uptime: 0 days 0:16:53.000
  Kernel time: 0 days 0:00:45.000
  User time: 0 days 0:02:48.000

上面来看,大约有17分钟被程序用掉了。我们看一下14号在作什么?

  14  Id: f28.11b8 Suspend: 1 Teb: fff88000 Unfrozen
ChildEBP RetAddr  Args to Child             
020afc8c 7d4d8c46 00000224 00000000 020afcd0 ntdll!ZwWaitForSingleObject+0x15
020afcfc 79e77fd1 00000224 00009c40 00000000 kernel32!WaitForSingleObjectEx+0xac
020afd40 79e77f9a 00000224 00009c40 00000000 mscorwks!PEImage::LoadImage+0x199
020afd90 79e77f50 00009c40 00000000 00000000 mscorwks!CLREvent::WaitEx+0x117
020afda0 79f5b69c 00009c40 00000000 00000000 mscorwks!CLREvent::Wait+0x17
020afe20 7a1121c3 001862f8 00009c40 00000000 mscorwks!ThreadpoolMgr::SafeWait+0x73
020afe94 79f71123 00000000 00000000 00000000 mscorwks!ThreadpoolMgr::WorkerThreadStart+0xf1
020affb8 7d4e0729 0019cab8 00000000 00000000 mscorwks!ThreadpoolMgr::intermediateThreadProc+0x49
020affec 00000000 79f710dd 0019cab8 00000000 kernel32!BaseThreadStart+0x34

哦,很清闲,什么都没干。但是,那17分钟中,有56秒在用着CPU,什么意思呢???我们继续看一下21号线程的托管堆。

0:021> !clrstack
OS Thread Id: 0x1244 (21)
ESP       EIP    
029ef1e8 7d61c824 [HelperMethodFrame: 029ef1e8]
029ef254 02920df3 LargeObjectHeap.GetLotsOfDatesXML(Int32)
029ef2a4 02920b20 LargeObjectHeap.Button1_Click(System.Object, System.EventArgs)
029ef2b4 6881bdc6 System.Web.UI.WebControls.Button.OnClick(System.EventArgs)
029ef2c8 6881bfbc System.Web.UI.WebControls.Button.RaisePostBackEvent(System.String)
029ef2dc 6881bf38 System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(System.String)
029ef2e0 687d91e0 System.Web.UI.Page.RaisePostBackEvent(System.Web.UI.IPostBackEventHandler, System.String)
029ef2e8 687d912a System.Web.UI.Page.RaisePostBackEvent(System.Collections.Specialized.NameValueCollection)
029ef2f8 687dcbbf System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
029ef4b0 687db521 System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
029ef4e0 687db487 System.Web.UI.Page.ProcessRequest()
029ef518 687db3a7 System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext)
029ef520 687db33a System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
029ef534 02920795 ASP.largeobjectheap_aspx.ProcessRequest(System.Web.HttpContext)
029ef538 686888df System.Web.HttpApplication+CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
029ef56c 6865a071 System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef)
029ef5ac 6865a39b System.Web.HttpApplication.ResumeSteps(System.Exception)
029ef5f4 6865912d System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(System.Web.HttpContext, System.AsyncCallback, System.Object)
029ef610 6865e0bd System.Web.HttpRuntime.ProcessRequestInternal(System.Web.HttpWorkerRequest)
029ef644 6865dd72 System.Web.HttpRuntime.ProcessRequestNoDemand(System.Web.HttpWorkerRequest)
029ef650 6865c447 System.Web.Hosting.ISAPIRuntime.ProcessRequest(IntPtr, Int32)
029ef800 79f1ef33 [ContextTransitionFrame: 029ef800]
029ef850 79f1ef33 [GCFrame: 029ef850]
029ef9a8 79f1ef33 [ComMethodFrame: 029ef9a8]

看上面的call stack,我们看到了LargeObjectHeap.GetLotsOfDatesXML(),这个咚咚触发了LOH的回收,然后按次序触发了2代、1代、0代。

但是只有一个大对象,并不会导致高CPU的占用啊!那我们看一下实际的代码吧!

    String GetLotsOfDatesXML(int i)

    {

        String dates = "<Dates>";

        for (int j = 0; j < i; j++)

        {

            DateTime dt = DateTime.Now.Add(new TimeSpan(j, 0, 0, 0));

            dates += "<Date><Month>" + dt.Month + "</Month><Day>" + dt.Day + "</Day><DayOfWeek>" + dt.DayOfWeek + "</DayOfWeek><Date>";

            dates += "<Date><Month>" + dt.Month + "</Month><Day>" + dt.Day + "</Day><DayOfWeek>" + dt.DayOfWeek + "</DayOfWeek><Date>";

        }

        dates += "</Dates>";

        return dates;

    }

如果外面传来的i小点还好,但是如果很大呢?对于确切地i是多少,我们需要一点一点地开始找。从callstack上面,我们看到是LargeObjectHeap.Button1_Click调用了GetLotsOfDatesXML方法。

    protected void Button1_Click(object sender, EventArgs e)

    {

        String str = GetLotsOfDatesXML(Int32.Parse(txtNumIterations.Text));

    }

在这里,txtNumIterations是一个textbox控件。下面,基本上都是!do和!dso的工作了。

0:021> !dso
OS Thread Id: 0x1244 (21)
ESP/REG  Object   Name
029ef0f0 0b1a2270 System.String    <String is invalid or too large to print>
029ef1a8 0b1a2270 System.String    <String is invalid or too large to print>
029ef1bc 0b1a2270 System.String    <String is invalid or too large to print>
029ef228 06a759e4 System.String    </DayOfWeek><Date>
029ef28c 06a76df0 ASP.largeobjectheap_aspx
029ef2b4 06a77b84 System.ComponentModel.EventHandlerList
029ef2bc 06a77a84 System.Web.UI.WebControls.Button
029ef2c8 06a76df0 ASP.largeobjectheap_aspx
029ef304 06a76880 System.Web.HttpContext
...

0:021> !do 06a76df0
Name: ASP.largeobjectheap_aspx
MethodTable: 02746ccc
EEClass: 028d24cc
Size: 380(0x17c) bytes
 (C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\debuggersamples\e7443224\5232f845\App_Web_h0ctkxwz.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790fa3e0  4001fe0        4        System.String  0 instance 02a7cba4 _id
790fa3e0  4001fe1        8        System.String  0 instance 00000000 _cachedUniqueID
68a2af44  4001fe2        c ...em.Web.UI.Control  0 instance 00000000 _parent
68a91070  4001fe3       2c         System.Int32  0 instance        5 _controlState
68a85ea0  4001fe4       10 ...m.Web.UI.StateBag  0 instance 00000000 _viewState
68a2af44  4001fe5       14 ...em.Web.UI.Control  0 instance 00000000 _namingContainer
68a273d0  4001fe6       18   System.Web.UI.Page  0 instance 06a76df0 _page
...
68a7d910  4000004      16c ...ebControls.Button  0 instance 06a77a84 Button1
68a95f40  4000005      170 ...bControls.TextBox  0 instance 06a77be4 txtNumIterations
68a2bc80  4000006      174 ...Controls.HtmlForm  0 instance 06a77688 form1

好,继续看一下这个textbox。

0:021> !do 06a77be4
Name: System.Web.UI.WebControls.TextBox
MethodTable: 68a95f40
EEClass: 68a95ebc
Size: 80(0x50) bytes
 (C:\WINDOWS\assembly\GAC_32\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790fa3e0  4001fe0        4        System.String  0 instance 02a85a04 _id
790fa3e0  4001fe1        8        System.String  0 instance 00000000 _cachedUniqueID
68a2af44  4001fe2        c ...em.Web.UI.Control  0 instance 06a77688 _parent
68a91070  4001fe3       2c         System.Int32  0 instance        5 _controlState
68a85ea0  4001fe4       10 ...m.Web.UI.StateBag  0 instance 06a788f0 _viewState
68a2af44  4001fe5       14 ...em.Web.UI.Control  0 instance 06a76df0 _namingContainer
68a273d0  4001fe6       18   System.Web.UI.Page  0 instance 06a76df0 _page
68a92e2c  4001fe7       1c ...+OccasionalFields  0 instance 06a78974 _occasionalFields
68a2b378  4001fe8       20 ...I.TemplateControl  0 instance 00000000 _templateControl
...

0:021> !do 06a788f0
Name: System.Web.UI.StateBag
MethodTable: 68a85ea0
EEClass: 68a85e30
Size: 16(0x10) bytes
 (C:\WINDOWS\assembly\GAC_32\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
79113dfc  400235f        4 ...tions.IDictionary  0 instance 06a78900 bag
79104f64  4002360        8       System.Boolean  0 instance        1 marked
79104f64  4002361        9       System.Boolean  0 instance        0 ignoreCase

0:021> !do 06a78900
Name: System.Collections.Specialized.HybridDictionary
MethodTable: 7a747ad4
EEClass: 7a7aa890
Size: 20(0x14) bytes
 (C:\WINDOWS\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
7a747bac  4001145        4 ...ed.ListDictionary  0 instance 06a78924 list
790fea70  4001146        8 ...ections.Hashtable  0 instance 00000000 hashtable
79104f64  4001147        c       System.Boolean  0 instance        0 caseInsensitive

0:021> !do 06a78924
Name: System.Collections.Specialized.ListDictionary
MethodTable: 7a747bac
EEClass: 7a7aa918
Size: 28(0x1c) bytes
 (C:\WINDOWS\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
7a747c78  4001148        4 ...ry+DictionaryNode  0 instance 06a78940 head
790fed1c  4001149       10         System.Int32  0 instance        1 version
790fed1c  400114a       14         System.Int32  0 instance        1 count
791117c8  400114b        8 ...ections.IComparer  0 instance 00000000 comparer
790f9c18  400114c        c        System.Object  0 instance 00000000 _syncRoot

0:021> !do 06a78940
Name: System.Collections.Specialized.ListDictionary+DictionaryNode
MethodTable: 7a747c78
EEClass: 7a7aa9b8
Size: 20(0x14) bytes
 (C:\WINDOWS\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790f9c18  4001158        4        System.Object  0 instance 06a66bd8 key
790f9c18  4001159        8        System.Object  0 instance 06a78914 value
7a747c78  400115a        c ...ry+DictionaryNode  0 instance 00000000 next

0:021> !do 06a66bd8
Name: System.String
MethodTable: 790fa3e0
EEClass: 790fa340
Size: 26(0x1a) bytes
 (C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: Text
...

找到了Text属性,看看是多少?

0:021> !do 06a78914
Name: System.Web.UI.StateItem
MethodTable: 68a131b4
EEClass: 68a13144
Size: 16(0x10) bytes
 (C:\WINDOWS\assembly\GAC_32\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790f9c18  4002362        4        System.Object  0 instance 06a78184 value
79104f64  4002363        8       System.Boolean  0 instance        1 isDirty

0:021> !do 06a78184
Name: System.String
MethodTable: 790fa3e0
EEClass: 790fa340
Size: 28(0x1c) bytes
 (C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: 40000
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790fed1c  4000096        4         System.Int32  0 instance        6 m_arrayLength
790fed1c  4000097        8         System.Int32  0 instance        5 m_stringLength
790fbefc  4000098        c          System.Char  0 instance       34 m_firstChar
790fa3e0  4000099       10        System.String  0   shared   static Empty
    >> Domain:Value  001a8868:790d6584 001ca990:790d6584 <<
79124670  400009a       14        System.Char[]  0   shared   static WhitespaceChars
    >> Domain:Value  001a8868:06a303f0 001ca990:06a34118 <<


靠,循环了4万次!

结论:

地球人都知道,用+=会产生3*n个对象,如果用StringBuilder,就不会有这个问题了。

posted on 2006-12-21 23:38:00 by juqiang  评论(18) 阅读(8441)

 
2006年12月18日

上文中,我们解决了那个场景的死锁问题。这次,我们分析一下,为什么会死锁呢?再回顾一下两个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,使得已提交读事务使用行版本控制。
    • 使用快照隔离。
  • 使用绑定连接。

posted on 2006-12-18 14:21:00 by juqiang  评论(18) 阅读(10626)

 

   死锁,简而言之,两个或者多个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,呵呵,死锁没有了。

   这种方式,我们可以解决大部分的Sql Server死锁问题。那么,发生这个死锁的根本原因是什么呢?为什么增加一个non clustered index,问题就解决了呢?且听下文分解。

posted on 2006-12-18 12:41:00 by juqiang  评论(23) 阅读(12766)

 
2006年12月16日

string bytecode = "73 01 00 00 06 28 4E 00 00 0A 2A";
上面这个string干什么的?加密后的代码?某种不可思议的数字?汇编语言?

如果按照CLR IL规范来解析,我们会得到一段常见的代码。
0000 : newobj instance void WindowsApplication3.MainForm::.ctor()
0005 : call System.Void System.Windows.Forms.Application::Run()
0010 : ret

简单吗?看起来是new一个MainForm,然后call Application的Run方法,最后return。
仔细想想,这就是我们常见的,每个winform程序中缺省方式下自动生成的代码。如下:
static void Main()
{
   Application.Run(new MainForm());
}
实际上等同于:
static void Main()
{
   MainForm mf = new MainForm();
   Application.Run(mf);
}


那么,上面那个bytecode,是如何被翻译成上述代码的?

哦,bytecode的第一个字符,就是73。有一个神奇的表格,其中一行:
73 newobj <Method> N arguments o

我们看到了,73代表一个东西叫做newobj,并且,它是一个method。我们应该能猜测出来,C#代码中应该就是一个new关键字。

第二个数字01,我们还是查找那个表格:
01 break - - -

哦,怎么是break呢?我们代码里面,不大可能new了一个对象之后,立刻就break啊?实际上,第一句newobj之后,执行的代码是从0x05,就是0x28开始(第二句) 。为什么跳跃了5个字节呢?因为IL中,并不只是包含operator,还包含了其他数据。在CLR解析过程中,它发现0x73是一个method之后,会继续取得一个所谓的metadataToken。伪代码如下:
metadataToken = ReadInt32(il, ref position);
而ReadInt32代码,如下所示:
private int ReadInt32(byte[] _il, ref int position)
{
   return (((il[position++] | (il[position++] << 8)) | (il[position++] << 0x10)) | (il[position++] << 0x18));
}

为什么会这么做???
暂且不表,我们继续。通过上面的ReadInt32,position增加了4,变成了5。所以要从bytecode第0个字节跳跃5个,直奔0x28。通过查表,我们知道,0x28代表着一个call:
28 call <Method> N arguments Ret.value。通过某种手段,我们可以得到当前0x28的call的方法名,是run。

那么首先,那个神奇的表格是什么呢?就是一个二维信息,从0x00到0xFE,每个字节分别对应着某个操作,如br,call,newobj等。这个表格,可以从《Inside Microsoft.NET IL Assembler eBook》中找到。
我们查表查找到operator之后,通过一系列的Read*方法,得到metadatatoken。针对这个token,分别进行resolve,得到最后的operand。如上所述,通过Read*系列方法,我们同时移动了bytecode中的offset,可以定位到正确的下一个operator。

这段解析代码,可以到codeproject上查找SDILReader,里面的Globals.cs、ILInstruction.cs、MethodBodyReader.cs,分别对应着全局operators、IL构造、方法转换等内容。

posted on 2006-12-16 15:53:00 by juqiang  评论(12) 阅读(6940)

 
2006年12月12日

龟速查询

阻塞和索引问题,是常见的导致sql以龟速执行的罪魁。

阻塞
阻塞主要等待逻辑锁,如请求一个X锁。关于锁的信息,遍地都是,msdn或者google都可以。
SQL Server 2005提供了125中等待类型(2000是76种)。

假设我们sp_who看到了一个block在56号上,那么通过这个可以看到详细信息

select * from sys.dm_os_waiting_tasks where session_id=56

(在2000下,你可以通过dbcc inputbuffer(56)来看当前执行的文本)

0x022A8898 56  0  1103500   LCK_M_S  0x03696820  0x022A8D48  53  NULL  ridlock fileid=1  pageid=143 dbid=9 id=lock3667d00 mode=X  associatedObjectId=72057594038321152

这里显示,56被53阻塞,并且等待了1103500毫秒了。

通过使用sys.dm_tran_locks,我们可以看到56被53以X模式锁住了,53持有1:143:3这个资源。

select request_session_id as spid, resource_type as rt,  resource_database_id as rdb,  (case resource_type WHEN 'OBJECT' then object_name(resource_associated_entity_id) WHEN 'DATABASE' then ' ' ELSE (select object_name(object_id)  from sys.partitions  where  obt_id=resource_associated_entity_id)    END) as objname,  resource_description as rd,     request_mode as rm, request_status as rs from sys.dm_tran_locks

输出如下

spid     rt           rdb         objname       rd            rm           rs

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

56    DATABASE     9                               S          GRANT

53    DATABASE     9                              S          GRANT

56    PAGE          9       t_lock      1:143       IS         GRANT

53    PAGE        9       t_lock      1:143       IX         GRANT

53    PAGE          9       t_lock      1:153       IX         GRANT

56   OBJECT       9       t_lock                  IS         GRANT

53   OBJECT       9        t_lock                 IX         GRANT

53    KEY         9        t_lock      (a400c34cb X          GRANT

53    RID         9        t_lock      1:143:3    X          GRANT

56    RID         9        t_lock      1:143:3    S       WAIT

而在SQL Server 2000里面,可以从sysprocesses看到。

select * from master..sysprocesses where blocked <> 0.

更详细的阻塞信息
2005中提供了一个新的DMV:Sys.dm_db_index_operational_stats,它提供了针对每个表、索引、分区的详细阻塞情况,如:访问历史、锁、阻塞、waits等。详细信息如下:

·         页/行等持有锁的个数

·         页/行等锁或waits的个数

·         页/行等锁或waits的时间

·         页闩的waits个数(闩与hotspot,就是所谓的热点有关,下同)

·         页闩的waits时间

·         页I/O闩的waits时间

除了阻塞的信息,还有一些索引的信息。

·         访问方式,如某个range,或者lookup

·         在叶子层的插入/更新/修改

·         叶子层之上的插入/更新/修改,就是索引维护。每个叶子页面的第一行,指向了该层的上一层。假如说在叶子上分配了一个新页面,那么上面那层的页面就包含一个指向该层第一行的指针信息。

 

在原文的附录B中,包含了一系列的索引信息相关的存储过程。下面是使用该sp的步骤

1.     使用init_index_operational_stats来初始化indexstats表

2.     使用insert_indexstats建立一个基线

3.     运行你的负载

4.     到合时的实际,使用insert_indexstats捕获索引状态的快照

5.     跑get_indexstats来分析索引状态。诸如很高的阻塞或者很高的waits,基本可以表明索引有问题。

这里列出了一些使用上述sp的例子。

·         所有数据库中,使用最多的前5个索引

       exec get_indexstats @dbid=-1, @top='top 5',  @columns='index, usage',  @order='index usage'

·         锁增长最多的前5个索引

     exec get_indexstats @dbid=-1, @top='top 5', @order='index lock promotions', @threshold='[index lock promotion attempts] > 0'

·         递减模式,前5个最大的行锁waits时间的索引状态

       exec get_indexstats @dbid=-1, @top='top 5', @order='avg row lock wait ms'

·         前10个,在dbid=5的数据库中,所有阻塞率大于10%的索引状态

       exec get_indexstats @dbid=-1, @top='top 10', @order='block %', @threshold='[block %] > 0.1'

注:sql2000没有提供任何关于对象或者索引使用状态

监视索引使用情况
2005中提供了一个非常有用的DMV:sys.dm_db_index_usage_stats,通过它我们可以找到哪些索引正在被当前query使用,或者没被使用。注意的是,这些数据仅保留在内存中,并没有被持久化存储。所以,如果sql发生down机了,这些数据就都丢掉了。当然,我们可以把这些save到表中,供日后分析。

索引上的操作分为两种:用户方式和系统方式。一个索引,通过dbid、对象id和索引id三列信息唯一标示。索引id为0的时候,代表一个heap table;1的时候,聚集索引;大于1的时候,非聚集索引。

 

2005下,seek/scan/lookup的规则与定义如下:

·         SEEK: 使用B-tree结构访问数据的次数。

·         SCAN: 不使用B-tree结构访问数据的次数。

·         LOOKUP: 使用不合适的非聚集索引配合聚集索引来寻找数据,如2000中的书签查找。

下面这个DMV可以得到“当前”所有数据库所有对象的索引状态。

select object_id, index_id, user_seeks, user_scans, user_lookups  from sys.dm_db_index_usage_stats  order by object_id, index_id

假设是下面的结果

object_id       index_id        user_seeks    user_scans    user_lookups

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

521690298         1                  0                 251                 123

521690298         2                123                 0                     0

 

该结果表明,有251次的聚集索引scan,123次的书签查找,123次的非聚集索引seek。

 

如果想知道从上次sql启动之后,到现在为止,某个表中没有被使用过的索引状况,执行下面的sql。

select i.name from sys.indexes i where i.object_id=object_id('<table_name>') and  i.index_id NOT IN  (select s.index_id  from sys.dm_db_index_usage_stats s  where s.object_id=i.object_id and  i.index_id=s.index_id and database_id = <dbid> )

 

所有未被使用的索引:

select object_name(object_id), i.name  from sys.indexes i  where  i.index_id NOT IN (select  s.index_id  from sys.dm_db_index_usage_stats s  where s.object_id=i.object_id and  i.index_id=s.index_id and database_id = <dbid> ) order by object_name(object_id) asc

结束了 

原文后面有N多的sp,大家可以参考原文,在你的sql2005上跑一次。当然,能够在客户的生产环境中,用这些sp进行实际测试,会得到更好的体会。

我个人认为,更好的利用这些DMVs和sp的前提是,对于sql的基本概念要有所了解。索引、锁、阻塞、死锁等,为什么会产生,他们在SqlServer这种数据库下面是如何处理的,等等。否则,看着那些DMVs,很容易发懵。

 

posted on 2006-12-12 11:36:00 by juqiang  评论(17) 阅读(8241)

 
2006年12月11日

TempDB
   每个实例只有一个tempdb,所以这里很可能成为性能或者磁盘空间的瓶颈。

   常见的tempdb问题如下:

   · 把磁盘空间用光了 

   · 因为tempdb的瓶颈,导致I/O很差。参见第一部分。 

   · DDL带来的对系统表的瓶颈 

   · 内容分配

   

   诊断问题之前,先看看tempdb是如何利用空间的。

   用户对象

      · 表和索引 

      · 全局临时表 (##t1)和索引 

      · 局部临时表和索引(#t1) and index. 

         · 当前连接的 

         · 存储过程内的 

      · 表变量(同上) 

   内部对象

      · Work file (hash join) 

      · Sort run 

      · Work table (cursor, spool和临时大对象)

   版本存储

      2005新增的

   空闲空间

      tempdb暂时没有用到的磁盘剩余空间.

   整个tempdb就是上述4个东西的和。

   监视tempdb剩余空间很简单,监测这个指标即可。Free Space in tempdb (KB)。下面这个DMVs很强大的说,上面四个都能看到。

   Select SUM (user_object_reserved_page_count)*8 as user_objects_kb,  SUM (internal_object_reserved_page_count)*8 as internal_objects_kb,  SUM (version_store_reserved_page_count)*8 as version_store_kb, SUM (unallocated_extent_page_count)*8 as freespace_kb  From sys.dm_db_file_space_usage  Where database_id = 2

   这是一个输出结果(kb表示的)

user_objets_kb internal_objects_kb version_store_kb freespace_kb
---------------- -------------------- ------------------ ------------
8736 128 64 448

分析空间使用问题
用户对象

   跑这个,能看出来到底谁干的。

   DECLARE userobj_cursor CURSOR FOR 
   select sys.schemas.name + '.' + sys.objects.name  from sys.objects, sys.schemas where object_id > 100 and  type_desc = 'USER_TABLE'and 
sys.objects.schema_id = sys.schemas.schema_id  
   go 
   open userobj_cursor 
   go 
   
   declare @name varchar(256) 
   fetch userobj_cursor into @name 
   while (@@FETCH_STATUS = 0) 
      begin 
         exec sp_spaceused @objname = @name 
         fetch userobj_cursor into @name 
      end 
   close userobj_cursor

版本存储

   select top 2 transaction_id, transaction_sequence_num, elapsed_time_seconds from sys.dm_tran_active_snapshot_database_transactions order by elapsed_time_seconds DESC

   这里显示一个带有XSN3的事务(ID是8609),已经激活了6523秒。

   transaction_id transaction_sequence_num elapsed_time_seconds 
   
-------------------- ------------------------ -------------------- 
   8609 3 6523 
   20156 25 783

   Kill掉第一个trans,我们可以释放iding数量的version store。但是,没有办法能够估计出来,kill掉之后,究竟能释放多少。

内部对象

   SQL Server 2005提供了两个DMVs: sys.dm_db_session_space_usage和 asys.dm_db_task_space_usage,用以跟踪sessions和tasks在tempdb中的空间变化。

select session_id, internal_objects_alloc_page_count, internal_objects_dealloc_page_count from sys.dm_db_session_space_usage order by internal_objects_alloc_page_count DESC

   再如这个

   SELECT t1.session_id, (t1.internal_objects_alloc_page_count + task_alloc) as allocated, (t1.internal_objects_dealloc_page_count + task_dealloc) as deallocated from sys.dm_db_session_space_usage as t1, (select session_id, sum(internal_objects_alloc_page_count) as task_alloc, sum (internal_objects_dealloc_page_count) as task_dealloc from sys.dm_db_task_space_usage group by session_id) as t2 where t1.session_id = t2.session_id and t1.session_id >50 order by allocated DESC

   这里一个示例输出

   session_id allocated deallocated 
   
---------- -------------------- -------------------- 
   52 5120 5136 
   51 16 0

   对于tasks,可以执行下面的sql

   select t1.session_id, t1.request_id, t1.task_alloc, t1.task_dealloc, t2.sql_handle, t2.statement_start_offset, t2.statement_end_offset, t2.plan_handle from (Select session_id, request_id, sum(internal_objects_alloc_page_count) as task_alloc, sum (internal_objects_dealloc_page_count) as task_dealloc from sys.dm_db_task_space_usage group by session_id, request_id) as t1, sys.dm_exec_requests as t2 where t1.session_id = t2.session_id and (t1.request_id = t2.request_id) order by t1.task_alloc DESC

   session_id request_id task_alloc task_dealloc 
   --------------------------------------------------------- 
   52 0 1024 1024

   sql_handle statement_start_offset 
   -----------------------------------------------------------------------    0x02000000D490961BDD2A8BE3B0FB81ED67655EFEEB360172 356

   statement_end_offset plan_handle 
   --------------------------------- 
   -1 0x06000500D490961BA8C19503000000000000000000000000

   然后通过sql_handle和plan_handle,就可以得到sql的语句

   select text from sys.dm_exec_sql_text(@sql_handle) 
   select * from sys.dm_exec_query_plan(@plan_handle)

 

   (第三部分,介绍大家最关心的sql调优)

posted on 2006-12-11 18:08:00 by juqiang  评论(4) 阅读(7960)

 

名次解释

DMVs:dynamic management views

三个点

· 资源瓶颈: CPU、内存、I/O(这里面不考虑网络问题)

· Tempdb瓶颈:

· User query瓶颈,可能是统计信息的变化、不恰当的索引、阻塞或者死锁等

上述三点,可能是相互影响的。

资源瓶颈

 

工具

1. System Monitor (PerfMon):windows自带

2. SQL Server Profiler: 2005继续有

3. DBCC commands: 参考联机文档

4. DMVs: 见上名次解释

 

CPU瓶颈

CPU瓶颈,是突然并且不可预料的。一般来讲,没有优化的查询计划、系统低配置、设计不合理等,很容易导致这些问题。

在perfmon中,我们一般需要监视Processor:% Processor Time,如果每个CPU持续高于80%,CPU就是瓶颈了。当然,在强大的2005下我们也可监视sys.dm_os_schedulers ,如果有内容,表明有任务等待CPU来分配给它。如下面这个DMVs的查询:

select scheduler_id,current_tasks_count,runnable_tasks_count from sys.dm_os_schedulers where scheduler_id < 255

下面的查询,更高级点。分析方法是,看结果的number_of_statements,如果该值大于1,说明可能有问题,要进一步分析。

select top 50 sum(qs.total_worker_time) as total_cpu_time, sum(qs.execution_count) as total_execution_count,count(*) as number_of_statements, qs.plan_handle from sys.dm_exec_query_stats qs group by qs.plan_handle order by sum(qs.total_worker_time) desc

执行计划的编译与重新编译

在sql2005中的一个改进,就是对于某个sp,进行recompile的时候,只需要针对改变的部分进行编译,sql2000只能是全部都搞一遍。

Recompile的原因很多,如:

· Schema的变更 changed

· Statistics变更

· 延迟编译

· SET option的执行

· 临时表的变化

· Sp使用了RECOMPILE提示或者使用了OPTION (RECOMPILE)

 

诊断方法,老朋友了,继续使用perfmon或者sql profiler。

对于perfmon,监视下面的 计数器

· SQL Server: SQL Statistics: Batch Requests/sec

· SQL Server: SQL Statistics: SQL Compilations/sec

· SQL Server: SQL Statistics: SQL Recompilations/sec

对于profiler抓到的trace,分析这几个event:SP:Recompile / SQL:StmtRecompile / Showplan XML For Query Compile。如果我们抓到了trace,对于文件,可以这么做:

select spid,StartTime,Textdata,EventSubclass,ObjectID,SQLHandle from fn_trace_gettable ( 'e:\recompiletrace.trc' , 1) where EventClass in(37,75,166)

这里面,EventClass 37 = Sp:Recompile, 75 = CursorRecompile, 166=SQL:StmtRecompile

如果你事先保存到了 table jq中,那么把上面的from修改为from jq即可。

或者使用这个DMVs: sys.dm_exec_query_optimizer_info(注意一个技巧!多执行几次,看中间的差异)

select * from sys.dm_exec_query_optimizer_info

另外一个DMVs是:sys.dm_exec_query_stats如执行这个sql:

select * from sys.dm_exec_query_stats CROSS APPLY sys.dm_exec_sql_text(sql_handle) AS s2

另外, plan_generation_num标示出了被recompile的所有query。如下面这个

select top 25 sql_text.text,sql_handle,plan_generation_num, execution_count,dbid,objectid from sys.dm_exec_query_stats a cross apply sys.dm_exec_sql_text(sql_handle) as sql_text where plan_generation_num >1 order by plan_generation_num desc

解决办法

· 如果因为使用了Set option,那么通过profiler来观察是什么引起的变化。

· 如果因为临时表,那么尽量使用表变量,而不是临时表。(对于表变量的限制,请查看联机文档)另一个解决办法,使用KEEP PLAN查询提示,这会把临时表当作普通表一样对待,会进行statistics的跟踪

· 关闭对于索引或者索引视图上的状态自动更新(偶个人不建议)

· 对象名称写全了,如dbo.Table1

· 尽量少用延迟编译。如你的SP或者query里面,有N多的if/else之类的。

· 运行索引调优向导(sql2000里面就有)

· 看看sp是不是使用了WITH RECOMPILE来建立的,或者RECOMPILE查询提示。

 

弱智的查询计划

每个查询执行之前,sqlserver都会“试图”优化一个最快的查询计划出来。注意的是,这里的最快的,不代表I/O最小,也不代表CPU占用最小。它是一个权衡后的值。

对于Hash join或者sort等,它们都是与CPU密切相关的。对于nested loop,很可能会因为大量的index lookups,导致I/O迅速上涨。如果search的数据散落在各个pages里面,很可能会导致缓冲命中率下降。

诊断方法

使用这个DMVs: sys.dm_exec_query_stats,它可以有效地监视CPU的使用情况。

select highest_cpu_queries.plan_handle, highest_cpu_queries.total_worker_time,q.dbid,q.objectid,q.number,q.encrypted,q.[text] from (select top 50 qs.plan_handle, qs.total_worker_time from sys.dm_exec_query_stats qs order by qs.total_worker_time desc) as highest_cpu_queries cross apply sys.dm_exec_sql_text(plan_handle) as q order by highest_cpu_queries.total_worker_time desc

解决办法

· 使用优化向导调优

· 检查一下,WHERE条件是不是限制的足够好?

 

游标问题

强烈建议,尽量减少使用游标。可以使用perfmon监视SQL Server:Cursor Manager By Type – Cursor Requests/Sec。或者使用DMVs:

select cur.* from sys.dm_exec_connections con cross apply sys.dm_exec_cursors(con.session_id) as cur where cur.fetch_buffer_size = 1 and cur.properties LIKE 'API%'

如果使用profiler,可以监视sp_cursorfetch(前提是包含了RPC:Completed这个event class)

内存瓶颈

对于VAS和AWE概念,请自行查找MSDN。我印象中,M$的人强烈建议不要在32bit windows上面使用AWE或者3BG之类的东西。

检测内存问题

打开taskmgr看物理内存中的Avaiable,如果持续低于10M,恭喜你,系统内存压力太大!通过perfmon,监视Memory: Available Mbytes,一样的效果。

对于AWE使用,可以用这个DMVs来看:

select sum(awe_allocated_kb) / 1024 as [AWE allocated, Mb]  from sys.dm_os_memory_clerks

对于虚拟内存等,可以观察Commit Charge中的Total,与limit的对比。如果两者很接近,虚拟内存可能不够了。如果你好奇,想看看sqlserver自己的内存分配,可以执行DBCC MEMORYSTATUS。具体内容,见联机文档。

对于内存问题,偶认为采用sql的默认设置,一般情况下足够了。

I/O瓶颈

除非操作系统能够,并且内存足够大,把你的db放到物理内存里,否则,I/O我们永远回避不过去。使用perfmon的话,可以监视

· PhysicalDisk Object: Avg. Disk Queue Length,如果经常性的大于2*磁盘个数,磁盘有性能问题。

· Avg. Disk Sec/Read,如果<10ms,很好。20以下,一般。50以下,密切观察。50以上,换硬盘吧!

· Avg. Disk Sec/Write,这个和上面的两个值,如果持续大于物理磁盘的指标的85%,说明磁盘已经到极限了。

· Physical Disk: %Disk Time,一般如果超过50%,I/O有瓶颈。

如果用了raid,采用下面这个公式来计算:

Raid 0 -- I/Os per disk = (reads + writes) / number of disks

Raid 1 -- I/Os per disk = [reads + (2 * writes)] / 2

Raid 5 -- I/Os per disk = [reads + (4 * writes)] / number of disks

Raid 10 -- I/Os per disk = [reads + (2 * writes)] / number of disks

如下面这个例子,2个磁盘,raid1,监测到的结果:

Disk Reads/sec            80

Disk Writes/sec           70

Avg. Disk Queue Length    5

那么I/O平均是80/2+70=110,队列长度上限是2*2=4

解决办法

· 检查sqlserver的内存配置

· 增加或者替换更快的硬盘,读写缓存越高越好

· 检查执行计划,找到I/O大的地方。如这个DMVs

select top 50 (total_logical_reads/execution_count) as avg_logical_reads, (total_logical_writes/execution_count) as avg_logical_writes, (total_physical_reads/execution_count) as  avg_phys_reads, Execution_count, statement_start_offset as stmt_start_offset, sql_handle, s2.text plan_handle from sys.dm_exec_query_stats cross APPLY sys.dm_exec_sql_text(sql_handle) AS s2 order by  (total_logical_reads + total_logical_writes) Desc

 

小TIP,如果要清除缓存的作用,执行这个:

checkpoint

dbcc freeproccache

dbcc dropcleanbuffers

(第二部分,介绍tempdb的优化)

(原文地址,点这里

posted on 2006-12-11 17:00:00 by juqiang  评论(9) 阅读(9654)

 
【第1页/共2页,32条】
首页
前页
1

Powered by: Joycode.MVC引擎 0.5.2.0