優化中發現一個存儲過程執行20秒通過profiler 抓取發現時間主要消耗在一個select * from 表,那么問題來了select幾萬數據竟然花了將近20秒?
問題排查清了程序前端使用了datareader獲取數據,那么datareader對數據庫有什么影響呢?下面來做個實驗測試一下。首先我們創建測試表並插入200條數據。
1 CREATE TABLE [dbo].[table_2]( 2 [a] [int] NULL, 3 [b] [datetime] NULL, 4 [c] [uniqueidentifier] NOT NULL 5 ) 6 7 insert into [table_2] 8 select 1,getdate(),newid() 9 go 200
編寫一段簡單的C#小程序,使用datareader讀取一個select * from table 在datareader的循環中我們設置線程等待50毫秒。System.Threading.Thread.Sleep(50);
protected void Button1_Click(object sender, EventArgs e) { //SqlCommand sqlCmd = new SqlCommand("p_datareader_test", con); //sqlCmd.Connection = con; //sqlCmd.CommandType = CommandType.StoredProcedure;//設置調用的類型為存儲過程 //SqlParameter sqlParme = new SqlParameter(); //sqlParme = sqlCmd.Parameters.Add("@p", SqlDbType.Int); //sqlParme.Direction = ParameterDirection.Input; //sqlParme.Value = 0; //SqlDataReader reader = sqlCmd.ExecuteReader(); SqlConnection con = new SqlConnection(); con.ConnectionString = "server=vpc-new3;database=replication;uid=sa;pwd=sa_123456"; DataTable dt = new DataTable(); //SQL直接查詢測試------------------------------- con.Open(); SqlCommand com = new SqlCommand(); com.Connection = con; com.CommandType = CommandType.Text; com.CommandText = "select * from table_2"; SqlDataReader reader = com.ExecuteReader(); DataRow dtr = dt.NewRow () ; try { DataTable objDataTable = new DataTable(); int intFieldCount = reader.FieldCount; for (int intCounter = 0; intCounter < intFieldCount; ++intCounter) { objDataTable.Columns.Add(reader.GetName(intCounter), reader.GetFieldType(intCounter)); } objDataTable.BeginLoadData(); object[] objValues = new object[intFieldCount]; while (reader.Read()) { //系統等待 System.Threading.Thread.Sleep(50); reader.GetValues(objValues); objDataTable.LoadDataRow(objValues, true); } reader.Close(); objDataTable.EndLoadData(); GV .DataSource = objDataTable; GV.DataBind(); } catch (Exception ex) { throw new Exception("轉換出錯!", ex); } reader.Close(); con.Close(); }
下面來測試執行一下:
點擊按鈕出發btnclick
執行時間0毫秒?竟然不像想象中的時間會長? 這是為什么呢?
測試繼續! 我們加大數據,繼續添加200條。
insert into [table_2] select 1,getdate(),newid() go 200
測試效果一樣0毫秒。
繼續增加200 條 目前數據600條
看一下效果:
效果神奇的出現了 600條記錄的查詢需要12秒!!
我們再一次執行觀察一下竟然12秒才執行完肯定會有等待!
select wait_type,brt.text from sys.dm_exec_requests br OUTER APPLY sys.dm_exec_sql_text(br.sql_handle) AS brt where brt.text like '%table_2%'
沒錯就是他 傳說中的ASYNE_NETWORK_IO
疑問:按照之前的理解datareader 一次只是讀取一條記錄...那么為什么我400條記錄的時候就不會出現等待呢?
想到了發送的網絡包大小,mssql默認為4096
那么我們繼續用200條數據測試這次加大表的長度
drop table table_2 CREATE TABLE [dbo].[table_2]( [a] [int] NULL, [b] [datetime] NULL, [c] [uniqueidentifier] NOT NULL, d char(4000) ) insert into [table_2] select 1,getdate(),newid(),'test' go 200
再次測試執行觀察效果
好吧我猜對了....
后續用dataset接收返回結果就不會出現上述問題,所以如網上所說datareader接收結果集使用要注意接收結果后要避免在循環中有大量的耗時處理。