问题

现在大多数的Web 应用都需要连接到数据库, 对数据库的数据进行操作. 有些时候Web应用对数据库的数据进行操作时, 会发生一些性能问题. 这个时候如果能找到一些有用的数据, 对性能调优是非常有用的. 例如 SQL Connection, Connection String, SQL Command Text. 本文将介绍如何从DUMP中找到这些数据.
以下是我们的示例代码. 这是非常且简单的步骤. 打开一个连接, 执行一条语句.

public void ExecuteReader()
        {
            String connectionString = @"Database=MyDataBaseMASTER;Server=MyTestServer;UID=myUID20130531;Min Pool Size=5;Max Pool Size=100;Connect Timeout=100;Password=Password!01";

            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                String commandText = "sp_MySqlProcedure";
                conn.Open();

                using (SqlCommand  sqlCommand = new SqlCommand(commandText,conn))
                {
                    sqlCommand.CommandType = CommandType.StoredProcedure;
                    using (IDataReader reader = sqlCommand.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            // do something
                        }
                    }
                }
            }
        }

场景: 有一个页面调用到了这个ExecuteReader Method. 但是页面一直白屏, 没有任何的反应. 在问题发生的时候, 抓了一个DUMP.现在我们并不清楚发生问题的SQL语句是什么, 也不知道这个Web应用的连接字符串是什么. Connection Pool是不是已经满了. 我们期望从DUMP里面看出一些线索.

步骤

1. 首先我们需要加载SOS.DLL. 实例应用为64-bit, .net framework 2.0, SOS.dll 的路径为C:\Windows\Microsoft.NET\Framework64\v2.0.50727\SOS.dll

0:087> .load C:\Windows\Microsoft.NET\Framework64\v2.0.50727\sos.dll

2. 找到发生问题的线程, 执行!clrstack可以检查CLR 的CALLSTACK. 这个线程当前已经将请求发送到了数据库, 正在做数据读取的动作. 是什么原因导致这个线程一直处于读取状态?
我们下一步希望从这个DUMP中知道所执行的SQL 语句以及SQL Connection方面的信息。

0:087> !clrstack
OS Thread Id: 0x2a3c (87)
Child-SP         RetAddr          Call Site
0000000020b3d1e0 000007fee09e0b5b SNINativeMethodWrapper.SNIReadSync(System.Runtime.InteropServices.SafeHandle, IntPtr ByRef, Int32)
0000000020b3d2e0 000007fee09e09fa System.Data.SqlClient.TdsParserStateObject.ReadSni(System.Data.Common.DbAsyncResult, System.Data.SqlClient.TdsParserStateObject)
0000000020b3d380 000007fee09e2efe System.Data.SqlClient.TdsParserStateObject.ReadNetworkPacket()
0000000020b3d3e0 000007fee09e2ecc System.Data.SqlClient.TdsParserStateObject.ReadBuffer()
0000000020b3d410 000007fee09e2924 System.Data.SqlClient.TdsParserStateObject.ReadByte()
0000000020b3d440 000007fee09d7b4a System.Data.SqlClient.TdsParser.Run(System.Data.SqlClient.RunBehavior, System.Data.SqlClient.SqlCommand, System.Data.SqlClient.SqlDataReader, System.Data.SqlClient.BulkCopySimpleResultSet, System.Data.SqlClient.TdsParserStateObject)
0000000020b3d510 000007fee09d7410 System.Data.SqlClient.SqlDataReader.ConsumeMetaData()
0000000020b3d560 000007fee09d43c9 System.Data.SqlClient.SqlDataReader.get_MetaData()
0000000020b3d5e0 000007fee09d42ad System.Data.SqlClient.SqlCommand.FinishExecuteReader(System.Data.SqlClient.SqlDataReader, System.Data.SqlClient.RunBehavior, System.String)
0000000020b3d660 000007fee09d3d48 System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(System.Data.CommandBehavior, System.Data.SqlClient.RunBehavior, Boolean, Boolean)
0000000020b3d740 000007fee09d3b8c System.Data.SqlClient.SqlCommand.RunExecuteReader(System.Data.CommandBehavior, System.Data.SqlClient.RunBehavior, Boolean, System.String, System.Data.Common.DbAsyncResult)
0000000020b3d7f0 000007fee09d39c3 System.Data.SqlClient.SqlCommand.RunExecuteReader(System.Data.CommandBehavior, System.Data.SqlClient.RunBehavior, Boolean, System.String)
0000000020b3d830 000007fee09d37b3 System.Data.SqlClient.SqlCommand.ExecuteReader(System.Data.CommandBehavior, System.String)
0000000020b3d8e0 000007ff004cedf1 System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(System.Data.CommandBehavior)
0000000020b3e0a0 000007fef60432b3 ASP.mytestpage_aspx.btnExecute_Click(System.Object, System.EventArgs)
0000000020b3e130 000007fef6042ecc System.Web.UI.WebControls.Button.OnClick(System.EventArgs)
0000000020b3e170 000007fef57e207d System.Web.UI.WebControls.Button.RaisePostBackEvent(System.String)
0000000020b3e1c0 000007fef57fb475 System.Web.UI.Page.RaisePostBackEvent(System.Web.UI.IPostBackEventHandler, System.String)
0000000020b3e1f0 000007fef57fa750 System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
0000000020b3e2c0 000007fef57fa67b System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
0000000020b3e320 000007fef57fa610 System.Web.UI.Page.ProcessRequest()
0000000020b3e380 000007ff023fb639 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
0000000020b3e3e0 000007fef5801ab7 ASP.mytestpage_aspx.ProcessRequest(System.Web.HttpContext)

3. 由于这个线程与SQL COMMAND的执行有关. 执行!dso命令, 从heap中找到SqlCommand对象. 并且对这个对象进行检查.

0:087> !dso
OS Thread Id: 0x2a3c (87)
RSP/REG          Object           Name
0000000020b3d178 00000001cfc81600 System.Data.SqlClient.SqlCommand

0:087> !do 00000001cfc81600 
Name: System.Data.SqlClient.SqlCommand
MethodTable: 000007fee0a2d180
EEClass: 000007fee0896478
Size: 224(0xe0) bytes
 (C:\Windows\assembly\GAC_64\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll)
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fef72573f8  400018a        8        System.Object  0 instance 0000000000000000 __identity
000007fef66b9ba8  40008e0       10 ...ponentModel.ISite  0 instance 0000000000000000 site
000007fef66e94a0  40008e1       18 ....EventHandlerList  0 instance 0000000000000000 events
000007fef72573f8  40008df      208        System.Object  0   shared           static EventDisposed
                                 >> Domain:Value  0000000002384230:NotInit  000000000245bf00:000000019f8ae5c0 <<
000007fef725ed78  40016f3       b0         System.Int32  1 instance          9892277 ObjectID
000007fef7257b08  40016f4       20        System.String  0 instance 00000001200c00c0 _commandText
000007fee0a2e7f8  40016f5       b4         System.Int32  1 instance                4 _commandType

0:087> !do 00000001200c00c0 
Name: System.String
MethodTable: 000007fef7257b08
EEClass: 000007fef6e5e550
Size: 56(0x38) bytes
 (C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: sp_MySqlProcedure

从SqlCommand对象, 我们可以看到_commandText里面的值是”sp_MySqlProcedure”. 这正是我们示例代码里面所付的值” String commandText = "sp_MySqlProcedure";”. 那么 我们怎么确定Command的类型? 我们在代码中定义的是” sqlCommand.CommandType = CommandType.StoredProcedure;”.

使用ILSpy反编译SqlCommand的代码. 可以确认CommandType会被赋值给_commandType. 这是一个enum. StoreProcedure的值为4.

4. 要找到ConnectionString. 首先需要需要先找到SqlConnection对象. 回到刚才的SqlCommand对象. 里面有个field “_activeConnection”. 这是当前的Connection.

0:087> !do 00000001cfc81600 
Name: System.Data.SqlClient.SqlCommand
MethodTable: 000007fee0a2d180
EEClass: 000007fee0896478
Size: 224(0xe0) bytes
 (C:\Windows\assembly\GAC_64\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll)
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
…..
000007fee0a2cad0  40016fe       38 ...ent.SqlConnection  0 instance 00000001cfc818e0 _activeConnection

0:087> !do 00000001cfc818e0 
Name: System.Data.SqlClient.SqlConnection
MethodTable: 000007fee0a2cad0
EEClass: 000007fee08963c0
Size: 104(0x68) bytes
 (C:\Windows\assembly\GAC_64\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll)
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fef72573f8  400018a        8        System.Object  0 instance 0000000000000000 __identity
000007fef66b9ba8  40008e0       10 ...ponentModel.ISite  0 instance 0000000000000000 site
000007fef66e94a0  40008e1       18 ....EventHandlerList  0 instance 0000000000000000 events
000007fef72573f8  40008df      208        System.Object  0   shared           static EventDisposed
                                 >> Domain:Value  0000000002384230:NotInit  000000000245bf00:000000019f8ae5c0 <<
000007fee0f42ca0  4000be5       20 ...hangeEventHandler  0 instance 0000000000000000 _stateChangeEventHandler
000007fee0f543a0  400172c       28 ...t.SqlDebugContext  0 instance 0000000000000000 _sdc
000007fef7256cd8  400172d       58       System.Boolean  1 instance                0 _AsycCommandInProgress
000007fee0a33528  400172e       30 ...ent.SqlStatistics  0 instance 0000000000000000 _statistics
000007fef7256cd8  400172f       59       System.Boolean  1 instance                0 _collectstats
000007fef7256cd8  4001730       5a       System.Boolean  1 instance                0 _fireInfoMessageEventOnUserErrors
000007fee0a305c0  4001733       38 ...ConnectionOptions  0 instance 000000019f8cae50 _userConnectionOptions
000007fee0a2fcd0  4001734       40 ...nnectionPoolGroup  0 instance 000000019f8cb720 _poolGroup

通过ILSpy来检查SQLCommand的代码. SQLConnection对象是复制给它的property : Connection. 在这个Property的get中, 该值赋给了_activeConnection. 这是我们从_activeConnection中找SQLConnection的原因.

5. SQL Connection的对象是从SQL Connection Pool里面取到. 连接池使新连接必须打开的次数得以减少. Pooler 保持物理连接的所有权. 通过为每个给定的连接配置保留一组活动连接来管理连接. 每当用户在连接上调用Open时, 池进程就会查找池中可用的连接。  如果某个池连接可用,会将该连接返回给调用者,而不是打开新连接。  应用程序在该连接上调用 Close 时, 池进程会将连接返回到活动连接池集中, 而不是关闭连接. 连接返回到池中之后, 即可在下一个 Open 调用中重复使用.

只有配置相同的连接可以建立池连接. ADO.NET 同时保留多个池, 每种配置各一个. 在使用集成的安全性时, 连接按照连接字符串以及 Windows 标识分到多个池中.

请参考 : http://msdn.microsoft.com/zh-cn/library/8xx3tyca.aspx

6. 从SQL Connection中找到连接字符串, 必须先找到ConnectionPoolOption对象. 从代码中能看到来自于_userConnectionOptions. ConnectionString保存在_usersConnectionString中.

0:087> !do 00000001cfc818e0 
Name: System.Data.SqlClient.SqlConnection
MethodTable: 000007fee0a2cad0
EEClass: 000007fee08963c0
Size: 104(0x68) bytes
 (C:\Windows\assembly\GAC_64\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll)
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fef72573f8  400018a        8        System.Object  0 instance 0000000000000000 __identity
000007fef66b9ba8  40008e0       10 ...ponentModel.ISite  0 instance 0000000000000000 site
000007fef66e94a0  40008e1       18 ....EventHandlerList  0 instance 0000000000000000 events
000007fef72573f8  40008df      208        System.Object  0   shared           static EventDisposed
                                 >> Domain:Value  0000000002384230:NotInit  000000000245bf00:000000019f8ae5c0 <<
000007fee0f42ca0  4000be5       20 ...hangeEventHandler  0 instance 0000000000000000 _stateChangeEventHandler
000007fee0f543a0  400172c       28 ...t.SqlDebugContext  0 instance 0000000000000000 _sdc
000007fef7256cd8  400172d       58       System.Boolean  1 instance                0 _AsycCommandInProgress
000007fee0a33528  400172e       30 ...ent.SqlStatistics  0 instance 0000000000000000 _statistics
000007fef7256cd8  400172f       59       System.Boolean  1 instance                0 _collectstats
000007fef7256cd8  4001730       5a       System.Boolean  1 instance                0 _fireInfoMessageEventOnUserErrors
000007fee0a305c0  4001733       38 ...ConnectionOptions  0 instance 000000019f8cae50 _userConnectionOptions


0:087> !do 000000019f8cae50 
Name: System.Data.SqlClient.SqlConnectionString
MethodTable: 000007fee0a304c0
EEClass: 000007fee08babc0
Size: 184(0xb8) bytes
 (C:\Windows\assembly\GAC_64\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll)
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fef7257b08  4000bef        8        System.String  0 instance 000000019f840ba8 _usersConnectionString

0:087> !do 000000019f840ba8 
Name: System.String
MethodTable: 000007fef7257b08
EEClass: 000007fef6e5e550
Size: 308(0x134) bytes
 (C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: Database=MyDataBaseMASTER;Server=MyTestServer;UID=myUID20130531;Min Pool Size=5;Max Pool Size=100;Connect Timeout=100;Password=Password!01

从ILSpy中检查SQLConnection.ConnectionString这个属性.  通过这个属性, 我们能在应用中取得ConnectionString. 而ConnectionString实际是来源与_userConnectionOptions(ConnectionPoolOption类型) 保存在中的_usersConnectionString (String 类型)

7. “既然Connection是从ConnectionPool里分配出来, 我们是否能从DUMP中看到当前已经分配的Connection数目?” 有些情况下, 程序员没有在使用完Connection之后, 显示的关闭Connection. 这些Connection或一直active直到TimeOut才会回到ConnectionPool中等待重用. 所以, 有些场合中, 我们也需要从DUMP中检查ConnectionPool已经分配了多少Connection.
首先需要从SqlConnection对象中找到_innerConeection. 再找到 _connectionPool. _connectionPool中有一个_totalObjects, 记录了分配了多少Connection. 如果这个值跟MaxConnection相等, ConnectionPool就不会再分配新的Connection.

0:087> !do 00000001cfc818e0 
Name: System.Data.SqlClient.SqlConnection
MethodTable: 000007fee0a2cad0
EEClass: 000007fee08963c0
Size: 104(0x68) bytes
 (C:\Windows\assembly\GAC_64\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll)
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fef72573f8  400018a        8        System.Object  0 instance 0000000000000000 __identity
000007fef66b9ba8  40008e0       10 ...ponentModel.ISite  0 instance 0000000000000000 site
_userConnectionOptions
000007fee0a2fcd0  4001734       40 ...nnectionPoolGroup  0 instance 000000019f8cb720 _poolGroup
000007fee0a30808  4001735       48 ...onnectionInternal  0 instance 00000001ab87b918 _innerConnection

0:087> !do 00000001ab87b918 
Name: System.Data.SqlClient.SqlInternalConnectionTds
MethodTable: 000007fee0a33e18
EEClass: 000007fee08bc040
Size: 248(0xf8) bytes
 (C:\Windows\assembly\GAC_64\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll)
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fef725ed78  4000f8a       38         System.Int32  1 instance              283 _objectID
000007fef7256cd8  4000f8d       44       System.Boolean  1 instance                0 _allowSetConnectionString
000007fef7256cd8  4000f8e       45       System.Boolean  1 instance                1 _hidePassword
000007fee0a38500  4000f8f       3c         System.Int32  1 instance                1 _state
000007fef724f020  4000f90        8 System.WeakReference  0 instance 00000001ab87ba10 _owningObject
000007fee0a30808  4000f91       10 ...onnectionInternal  0 instance 0000000000000000 _nextPooledObject
000007fee0a2ff20  4000f92       18 ....DbConnectionPool  0 instance 000000019f8cc558 _connectionPool

0:087> !do 000000019f8cc558 
Name: System.Data.ProviderBase.DbConnectionPool
MethodTable: 000007fee0a2ff20
EEClass: 000007fee0897080
Size: 176(0xb0) bytes
 (C:\Windows\assembly\GAC_64\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll)
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fef725ed78  400153c       88         System.Int32  1 instance           190000 _cleanupWait
000007fee0a337c8  400153d        8 ...ctionPoolIdentity  0 instance 000000019f8cc4d8 _identity
000007fee0a2f9c8  400153e       10 ...ConnectionFactory  0 instance 000000019f8ba208 _connectionFactory
000007fee0a2fcd0  400153f       18 ...nnectionPoolGroup  0 instance 000000019f8cb720 _connectionPoolGroup
000007fee0a30a58  4001540       20 ...nPoolGroupOptions  0 instance 000000019f8cb6f8 _connectionPoolGroupOptions
000007fee0f52588  4001541       28 ...nPoolProviderInfo  0 instance 0000000000000000 _connectionPoolProviderInfo
000007fee0f1ed90  4001542       8c         System.Int32  1 instance                1 _state
000007fee0a338a8  4001543       30 ...InternalListStack  0 instance 000000019f8cc7c8 _stackOld
000007fee0a338a8  4001544       38 ...InternalListStack  0 instance 000000019f8cc7e0 _stackNew
000007fef7246130  4001545       40 ...ding.WaitCallback  0 instance 000000019f8ccf48 _poolCreateRequest
000007fef7247070  4001546       48 ...Collections.Queue  0 instance 0000000000000000 _deactivateQueue
000007fef7246130  4001547       50 ...ding.WaitCallback  0 instance 0000000000000000 _deactivateCallback
000007fef725ed78  4001548       90         System.Int32  1 instance                0 _waitCount
000007fee0a33928  4001549       58 ...l+PoolWaitHandles  0 instance 000000019f8cc8e8 _waitHandles
000007fef7257dd0  400154a       60     System.Exception  0 instance 0000000000000000 _resError
000007fef7256cd8  400154b       a0       System.Boolean  1 instance                0 _errorOccurred
000007fef725ed78  400154c       94         System.Int32  1 instance             5000 _errorWait
000007fef7289e60  400154d       68 ...m.Threading.Timer  0 instance 0000000000000000 _errorTimer
000007fef7289e60  400154e       70 ...m.Threading.Timer  0 instance 000000019f8cd110 _cleanupTimer
000007fee0a33c58  400154f       78 ...tedConnectionPool  0 instance 000000019f8ccca8 _transactedConnectionPool
0000000000000000  4001550       80                       0 instance 000000019f8cc940 _objectList
000007fef725ed78  4001551       98         System.Int32  1 instance               12 _totalObjects

总结

1. SQL语句 以及 它的类型可以分别从System.Data.SqlClient.SqlCommand对象的_commandText 字段和 _commandType字段中找到.

2. SQLConnection是从ConnectionPool中分配出来.

3. 连接字符串, 以及相应的数据可以从ConnectionPoolOption对象中找到. 它位于SQLConnection对象的_userConnectionOptions字段下.

4. ConnectionPool已经分配的Connection数目, 可以从System.Data.ProviderBase.DbConnectionPool对象的_totalObjects字段中找到. 这个对象位于SQLConnection对象的_innerConnection下面.

 

希望以上内容对您有所帮助

Richard Chen

posted on 2013-05-28 10:47  微软互联网开发支持  阅读(2175)  评论(7编辑  收藏  举报