數據庫: sqlserver2008r2
表: device_data
數據量:2000w行左右
表結構
CREATE TABLE [dbo].[device_data]( [Id] [int] IDENTITY(1,1) NOT NULL, [DeviceId] [char](12) NOT NULL, [SystemTick] [int] NOT NULL, [Sport] [int] NOT NULL, [Temperature] [int] NOT NULL, [Voltage] [int] NOT NULL, [UploadTime] [datetime] NOT NULL, [CollectorMac] [char](8) NOT NULL, CONSTRAINT [PK__device_d__3214EC0770FDBF69] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
索引情況:分別有兩個聯合索引
idx_deviceid(DeviceId,UploadTime)
idx_collector(CollectorMac,UploadTime)
問題:這張表上傳的數據都是隨上傳時間遞增,批量有序插入進去。但是 最近幾天日志經常出現插入數據超時,然后去分析了一下數據庫,發現對外接口出現了慢 sql導致了死鎖,
慢sql是where條件在某些情況下沒有加上時間篩選過濾,2000萬的表導致非常慢,於是進行了優化,優化的時候變出現了下面的兩條sql語句
這兩個索引對應着兩個接口 查詢語句分別是:
select top 100 Id,DeviceId,Sport,Temperature,CollectorMac,UploadTime from device_data where [DeviceId]='C9C810B18272' and UploadTime>'2020-12-01 15:16:55.000' order by UploadTime desc; 和 select top 100 Id,DeviceId,Sport,Temperature,CollectorMac,UploadTime from device_data where [CollectorMac]='95DE5F0B' and UploadTime>'2020-12-01 15:16:55.000' order by UploadTime desc;
現在遇到一個情況是 相同的where條件下 如果我Order by Id Desc 就會發現返回結果中會比order by UploadTime desc慢一點,我一開始不太理解為啥會出現這個情況。
於是想到了sqlserver的查詢分析器來分析一下具體的sql執行計划
1,首先我來執行order by UploadTime desc的語句來看一下具體的執行計划
dbcc dropcleanbuffers set statistics io on select top 100 Id,DeviceId,Sport,Temperature,CollectorMac,UploadTime from device_data where [CollectorMac]='95DE5F0B' and UploadTime>'2020-12-01 15:16:55.000' order by UploadTime desc; set statistics io off
具體執行計划為:
DBCC 執行完畢。如果 DBCC 輸出了錯誤信息,請與系統管理員聯系。 (100 行受影響) 表 'device_data'。掃描計數 1,邏輯讀取 1899 次,物理讀取 5 次,預讀 24 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。 (1 行受影響)
2,再來看一下order by Id desc的具體執行計划
dbcc dropcleanbuffers set statistics io on select top 100 Id,DeviceId,Sport,Temperature,CollectorMac,UploadTime from device_data where [CollectorMac]='95DE5F0B' and UploadTime>'2020-12-01 15:16:55.000' order by Id desc; set statistics io off
具體執行計划為:
DBCC 執行完畢。如果 DBCC 輸出了錯誤信息,請與系統管理員聯系。 (100 行受影響) 表 'device_data'。掃描計數 1,邏輯讀取 40 次,物理讀取 36 次,預讀 4942 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。 (1 行受影響)
這里先說一下邏輯讀取和物理讀取等區別
那么,這幾個詞語代表什么意思呢?我們怎么根據這些來了解SQL語句或者存儲過程的I/O過程呢?
預讀:用於估計信息,去硬盤讀取數據到緩存。
物理讀:查詢計划生成以后,如果發現緩存缺少所需要的數據,讓緩存再次去讀硬盤數據。如果內存里沒有緩存數據或者執行計划(如果SQL語句發生了改變,
那么執行計划將不能重用,需要重新生成新的執行計划),那么SQLSERVER就要去硬盤讀取這些數據,這個時候就是物理讀取,我們大家都知道,硬盤速度
與內存速度根本不在一個數量級上,所以物理讀是比較慢的。
邏輯讀:SQLSERVER去內存里的緩存取數據或者執行計划,所以邏輯讀是比較快的。
SQLSERVER存儲的最小單位是頁,每一頁大小為8K,即8*1024=8192字節,SQLSERVER對頁的讀取是原子性的,即要么讀完一頁,要么完全不讀。即使
僅僅要獲得一條數據,也要讀完該頁,而頁之間的數據組織結構為B樹結構。所以SQLSERVER對於邏輯讀,物理讀,預讀的單位是頁。
可以看到bytime 走的是索引查找和鍵查找 並且大部分讀取走的是邏輯讀取
byid 走的是聚集索引掃描,並且大部分讀取走的是預讀取
看上面的介紹我們發現 邏輯讀取走的是內存緩存,預讀取是去硬盤讀取數據到緩存然后再邏輯讀取,在這一步我們基本確定了 byid慢的原因是讀取了硬盤數據。
那么上圖出現的索引查找和索引掃描這兩者又有什么區別呢
查看了相關資料發現
Clustered Index Scan(聚集索引掃描)、Index Scan(非聚集索引掃描)
聚集索引掃描:聚集索引的數據體積實際是就是表本身,也就是說表有多少行多少列,聚集所有就有多少行多少列,那么聚集索引掃描就跟表掃描差不多,也要進行全表掃描,遍歷所有表數據,查找出你想要的數據。
非聚集索引掃描:非聚集索引的體積是根據你的索引創建情況而定的,可以只包含你要查詢的列。那么進行非聚集索引掃描,便是你非聚集中包含的列的所有行進行遍歷,查找出你想要的數據。
Clustered Index Seek(聚集索引查找)、Index Seek(非聚集索引查找)
聚集索引查找和非聚集索引查找都是使用該圖標。
聚集索引查找:聚集索引包含整個表的數據,也就是在聚集索引的數據上根據鍵值取數據。
非聚集索引查找:非聚集索引包含創建索引時所包含列的數據,在這些非聚集索引的數據上根據鍵值取數據。
Key Lookup(鍵值查找)
首先需要說的是查找,查找與掃描在性能上完全不是一個級別的,掃描需要遍歷整張表,而查找只需要通過鍵值直接提取數據,返回結果,性能要好。
當你查找的列沒有完全被非聚集索引包含,就需要使用鍵值查找在聚集索引上查找非聚集索引不包含的列。
我們發現byid引起全量掃描了 所以會慢很多
疑問:我的理解是既然我已經加了where條件去篩選數據了 order by Id還是 order by Uploadtime 是不是應該在我where篩選出來的數據中再去排序,為啥只是因為Orderby的不同,最終的執行計划差別這么大
我對理論方面不深入,只能從現象來解決問題,還請大佬們賜教
參考資料: