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