现在大多数的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.

如何从DUMP中查找SQL Connection相关数据

如何从DUMP中查找SQL Connection相关数据

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的原因.

如何从DUMP中查找SQL Connection相关数据

如何从DUMP中查找SQL Connection相关数据

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 类型)

如何从DUMP中查找SQL Connection相关数据

如何从DUMP中查找SQL Connection相关数据

如何从DUMP中查找SQL Connection相关数据

如何从DUMP中查找SQL Connection相关数据

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

相关文章: