【問題描述】 開發反饋,有一個SQL Server數據同步的作業,從Table1 拉取數據,主鍵是ID, 每次拉取批次數據的SQL語句是 select top (15) from Table1(NOLOCK) where ID ?,?代表的是上次同步批次中最後一個ID號。 某一次拉取到的數據為 ID ...
【問題描述】
開發反饋,有一個SQL Server數據同步的作業,從Table1 拉取數據,主鍵是ID, 每次拉取批次數據的SQL語句是 select top (15) * from Table1(NOLOCK) where ID > ?,?代表的是上次同步批次中最後一個ID號。
某一次拉取到的數據為 ID: 8101102121,8101103081 兩條數據。查表發現,這兩條記錄中間,還有一條記錄,即ID=8101102855,這條記錄沒有被拉取到。十分困惑,為什麼會少拉一條記錄,是否拉取的時候,該條記錄沒有被創建?
Createtime | ID | Column3 | Column4 |
---|---|---|---|
2019-04-11 14:17:14.843 | 8101102121 | 已處理 | |
2019-04-11 14:17:17.190 | 8101102855 | 已處理 | |
2019-04-11 14:17:20.237 | 8101103081 | 已處理 |
【問題分析】
剛開始看這個問題,也覺得非常奇怪。這個查詢語句中規中矩,從應用日誌來看,兩個ID之間的8101102855 確實是沒有被拉取到。
為進一步定位問題,我們分析具體的查詢語句。由於我們的伺服器開啟了XEvent Trace,我們定位到,當時在資料庫伺服器端真正執行的語句如下,比開發反饋的更多一些條件:
select top 15 id from table1 with (nolock) where id > 8101101700 and Column4 not like '%(特殊需求)%' and createtime > '2018-07-19 00:00:00.000' order by id asc
我們用這個查詢,在當前時間(2019-04-12 17:23:00)資料庫上進行查詢,確實能返回三條記錄。
通過資料庫明細記錄, 該語句在資料庫上的執行時間是:2019-04-11 14:17:21。對於ID=8101102855的記錄,其插入的時間是2019-04-11 14:17:17.190,比我們的查詢時間早4秒,按道理應該是能查出來的。另外,在2019-04-11 14:17:21的時候,ID=8101102855 的記錄正在被更新。難道是我們的查詢帶了NOLOCK,所以當前正在被更新的記錄跳過了?
根據我們的理解,NOLOCK相當於Read uncommitted, 是會讀取其他事務“修改後未提交的“數據。也就是說,是能夠讀出主鍵ID的。
【問題重現】
為了能夠更好的分析問題,我們定點還原資料庫,還原到2019-04-11 14:17:20的時候,也就是比應用的查詢時間早1秒鐘。其數據記錄如下。在這個點上,ID=8101103081 還沒有插入進來,但ID=8101102855,也就是我們關註的ID,數據已經進來了。
Createtime | ID | Column3 | Column4 |
---|---|---|---|
2019-04-11 14:17:14.843 | 8101102121 | 已處理 | |
2019-04-11 14:17:17.190 | 8101102855 |
針對上面的數據,我們執行查詢:
select top 15 id from table1 with (nolock) where id > 8101101700 and Column4 not like '%(特殊需求)%' and createtime > '2018-07-19 00:00:00.000' order by id asc
奇怪的發現,這個查詢只返回ID=8101102121,ID=8101102855沒有返回。經過簡單的調試,我們很快發現,是由於這個查詢條件所致:
Column4 not like '%(特殊需求)%'
ID=8101102855記錄在剛插入的時候,其Column4的值為NULL,從語義上來講,確實是不包含"(特殊需求)"這個字元串的。但在SQL處理上,卻和我們理解的不一樣。
SQL除了IS NULL和NOT NULL以外,只要出現NULL,值結果為FALSE。簡單來說,對於查詢SELECT * from table where name != ‘test’,只要name值是NULL,無論用name=’test’還是name != 'test', 都不能返回這一行。如果要返回的話,需要加IS NULL判斷:
SELECT * from table where name != ‘test’ or name IS NULL
至此,問題真相大白,解決方案也就很簡單:調整查詢語句,加一個IS NULL判斷即可。
select top 15 id from table1 with (nolock) where id > 8101101700 and (Column4 not like '%(特殊需求)%' or Column4 IS NULL) and createtime > '2018-07-19 00:00:00.000' order by id asc
【結論】
資料庫在碰到NULL的處理時候,要小心,查詢的判斷條件並不是很明顯,要註意IS NULL的情況。