這一系列,咱們一塊兒來解剖SQLSERVERhtml
在系列的第一篇文章裏本人可能會得罪某些人,可是做爲一位SQLSERVER MVP,在個人MVP任期內但願能夠對你們做出一些貢獻mysql
在第一篇裏面涉及到某些內容可能不會以詳細的方式給出截圖而且和你們講解,畢竟第一篇的篇幅比較長,但願你們見諒。。git
在第一篇文章開始以前,先說三個題外話github
第一個題外話 更新丟失sql
首先要作的事情是,跟你們道歉數據庫
在以前《SQLSERVER走起》的微信公衆賬號裏推送了一篇文章,題目是《RDS-SQLSERVER的READ COMMITTED與READ_COMMITTED_SNAPSHOT的區別及各自優缺點》緩存
因爲當時沒有仔細看,就給你們推送了,文章裏面的建議確實是誤導了你們服務器
文章裏面這樣說道微信
舉個例子描述這個場景:session
T1事務發起一個修改,讀取原庫存是10,需求修改庫存減1,原庫存應該變成9,由於是READ_COMMITTED_SNAPSHOT隔離級別,因此數據庫會在tempdb裏生成一個快照,可是事務未提交,在這時發起了第二事務T2,也來修改庫存,由於看到事務T1未提交,因此他不能獲取未提交事務修改的值9(若是獲取9就是髒讀了),而是他獲取的是最後提交版本的庫存爲10,而正巧T2未提交前,T1先提交了,實際庫存應該變9而不是10,但T2事務獲取庫存值是10,假設T2的需求是減庫2,那麼最後T2提交後,會覆蓋T1事務所作的修改,庫存變成了8(咱們實際指望的是10-1-2=7),這樣就形成了邏輯混亂。
實際上,這種狀況不是邏輯混亂,這種狀況是屬於 「更新丟失」,你們隨便拿起一本SQLSERVER教科書,裏面都會有說到更新丟失這種現象
文章裏面說的解決方案基本上是錯誤的
錯誤一:提升事務隔離級別將會形成更加多的死鎖
錯誤二:沒有對「更新丟失」進行錯誤處理
個人好朋友高繼偉(博客園裏的shanks_gao)跟這個阿里雲SQLSERVER經理說過這個問題,可是最後他仍是沒有改過來
解決方案有兩種,都是在默認隔離級別 READ COMMITTED下,不須要修改默認隔離級別
第一種:使用try catch捕獲更新丟失
--示例 CREATE TABLE kucun(id INT PRIMARY KEY,qty INT,product NVARCHAR(20))
--插入一些測試數據 SELECT * FROM dbo.kucun ----------------------------------------------- --session 1 BEGIN TRAN DECLARE @qty INT SELECT @qty=qty FROM kucun WITH(UPDLOCK) WHERE [product]='牙膏' SELECT @qty UPDATE kucun SET qty=@qty-1 WHERE [product]='牙膏' COMMIT TRAN ------------------------------------------ --session 2 BEGIN TRAN DECLARE @qty INT SELECT @qty=qty FROM kucun WITH(UPDLOCK) WHERE [product]='牙膏' --阻塞 SELECT @qty --session 1提交以後才能夠讀,可是後面的update語句不會執行,這個時候更新丟失,使用try catch機制來捕獲更新丟失 UPDATE kucun SET qty=@qty-1 WHERE [product]='牙膏' COMMIT TRAN
第二種: 若是使用的是SQLSERVER2008 可使用merge語句來執行這個原子操做
--session 1 BEGIN TRAN MERGE [dbo].[kucun] AS TGT USING [dbo].[kucun] AS SRC ON TGT.product = SRC.product AND TGT.id = SRC.id AND TGT.product = '牙膏' WHEN MATCHED THEN UPDATE SET TGT.qty = TGT.qty - 1; COMMIT TRAN SELECT * FROM [dbo].[kucun] WHERE product = '牙膏'
--session 2 --當session 1沒有提交的時候就會阻塞,當session 1提交的時候 session 2 也能成功update記錄 ,不會形成更新丟失 BEGIN TRAN MERGE [dbo].[kucun] AS TGT USING [dbo].[kucun] AS SRC ON TGT.product = SRC.product AND TGT.id = SRC.id AND TGT.product = '牙膏' WHEN MATCHED THEN UPDATE SET TGT.qty = TGT.qty - 1; COMMIT TRAN
但願你們升級一下SQLSERVER,使用SQLSERVER2008提供的最新的merge語句,由於merge語句確實可以減小不少沒必要要的麻煩,並且性能也會有提高
看到這裏,可能你們對這種最基礎最基礎的知識不覺得然,可是你們試想一下,剛好這種最基礎的東西就有可能帶來致命的後果
例子:
好比你的銀行帳戶裏有100萬,你取出來了20萬,還剩下80萬
可是恰好遇到更新丟失,你的帳戶裏面可能已經取出了錢可是系統裏面沒有扣取你的錢又或者扣除多了 、扣除少了
後果可大可小
還有庫存系統,這裏就不說了
你們可能以爲「樺仔想借用這個例子,趁機貶低他人來擡高本身」 。實際上,我對於這個經理也是很理解,
當你管理成千成萬臺服務器的時候,你的腦子裏就會想到數據庫架構、集羣搭建、容災、業務連續性。。。
這是數據庫架構師要作的事,很難會顧及到這些基礎的東西,我本身也是管理着公司不少的數據庫
可是做爲數據庫專家,你給客戶的建議應該要足夠專業吧???
第二個題外話 估計行數
腳本
USE [sss] SELECT @@VERSION --Microsoft SQL Server 2005 - 9.00.4035.00 (X64) --Nov 24 2008 16:17:31 --Copyright (c) 1988-2005 Microsoft Corporation --Developer Edition (64-bit) on Windows NT 6.1 (Build 7601: Service Pack 1) CREATE TABLE teststat(id INT,name NVARCHAR(20)) --首先插入5條記錄 INSERT INTO teststat SELECT 1 ,'nihao' UNION ALL SELECT 2 ,'dajiahao' UNION ALL SELECT 3 ,'nihao' UNION ALL SELECT 4 ,'dajiahao' UNION ALL SELECT 5 ,'nihao' --顯示實際執行計劃 SELECT * FROM teststat --預估行數5條 --查看緩存的執行計劃 SELECT [cacheobjtype] , [objtype] , [usecounts] , [sql] FROM sys.[syscacheobjects] WHERE [sql] NOT LIKE '%cache%' AND [sql] LIKE '%INSERT INTO%' --再插入100條記錄 INSERT INTO teststat(id,name) SELECT 6,'dajiahao' GO 100 --查詢緩存的執行計劃 SELECT [cacheobjtype] , [objtype] , [usecounts] , [sql] FROM sys.[syscacheobjects] WHERE [sql] NOT LIKE '%cache%' AND [sql] LIKE '%INSERT INTO%' --顯示實際執行計劃 SELECT * FROM teststat --預估行數仍是5條 --清空編譯計劃 DBCC FREEPROCCACHE GO --顯示實際執行計劃 SELECT * FROM teststat --預估行數變成105條
第一次查看緩存的執行計劃
第二次查看緩存的執行計劃
清空plan cache以後
注意:DBCC FREEPROCCACHE是清空實例級別的計劃緩存,請不要隨意在生產環境下執行
第三個題外話 性能太差的SQLSERVER
某一天,開發又抱怨了:「SQLSERVER很慢,查詢要差很少9秒,這個問題怎麼完全解決!」
這個例子不是證實SQLSERVER多牛逼,只是爲了說明數據量大的時候,SQLSERVER也能夠應付
開發查詢的是一張xxclassifyxx表,表數據1.8億+
查詢語句以下
SELECT TOP 500 * FROM DBO.xxCLASSIFYxx with (nolock) WHERE ID>5102332830 ORDER BY ID
表狀況:
彙集索引創建在ID列上
上面的查詢大概須要8秒
你們看到這個SQL語句可能一開始並無什麼頭緒,但你們會發現,彙集索引既然創建在ID這一列上,那麼ORDER BY ID是否是有點多餘呢???
語句修改以前
執行時間 8秒 (500 行受影響) 表 'xxClassifyxx'。掃描計數 113,邏輯讀取 619665 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。
語句修改以後,只是去掉了ORDER BY ID
SELECT TOP 500 * FROM DBO.xxCLASSIFYxx with (nolock) WHERE ID>5102332830
(500 行受影響) 表 'xxClassifyxx'。掃描計數 1,邏輯讀取 32 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。
修改以後所用時間
在這裏,加ORDER BY ID和不加ORDER BY ID對於SQLSERVER來看是不一樣的
正題
按照公式 邏輯頁面號 * 8192 來算出偏移位置
這個計算公式沒有錯,只是,若是你真的要靠這個公式來找數據頁面,不少時候會找不到
例如沒有考慮到邏輯碎片的問題,並且咱們平時搜索數據的時候通常都不會使用這個公式
還有你們在研究的時候不要將DBCC PAGE的輸出中左邊的偏移值和winhex中左邊的偏移值對應起來
DBCC PAGE
WINHEX
測試腳本
咱們看下怎麼用VS查看數據文件內容
咱們使數據庫脫機,而後把mdf文件拖入VS
用winhex修改數據,選中要修改的數據的字節區域,而後右鍵—》edit-》你能夠選擇剪切、添加字節、填充零等等
用Visual Studio修改數據更簡單,選中要修改的字節區域,而後直接輸入16進制數就能夠了
在修改數據以前,把頁面校驗設置爲NONE
ALTER DATABASE [sss] SET PAGE_VERIFY NONE
若是你修改數據頁頭的話,在修改完畢以後數據庫還能夠聯機,可是你修改數據行的話,問題就嚴重了
消息 824,級別 24,狀態 2,第 1 行 SQL Server 檢測到基於一致性的邏輯 I/O 錯誤 校驗和不正確(應爲: 0xb70c8233,但實際爲: 0xb7438233)。在文件 'E:\DataBase\sss.mdf' 中、偏移量爲 0x0000000009a000 的位置對數據庫 ID 8 中的頁 (1:77) 執行 讀取 期間,發生了該錯誤。SQL Server 錯誤日誌或系統事件日誌中的其餘消息可能提供了更詳細信息。這是一個威脅數據庫完整性的嚴重錯誤條件,必須當即糾正。請執行完整的數據庫一致性檢查(DBCC CHECKDB)。此錯誤能夠由許多因素致使;有關詳細信息,請參閱 SQL Server 聯機叢書。
消息 5028,級別 16,狀態 4,第 1 行 系統沒法激活足夠的數據庫來重建日誌。 sss的 DBCC 結果。 CHECKDB 在數據庫 'sss' 中發現 0 個分配錯誤和 0 個一致性錯誤。 消息 7909,級別 20,狀態 1,第 1 行 緊急模式修復失敗。您必須從備份中還原。 消息 601,級別 12,狀態 3,第 1 行 因爲數據移動,沒法繼續以 NOLOCK 方式掃描。 消息 926,級別 14,狀態 1,第 1 行 沒法打開數據庫 'sss'。恢復操做已將該數據庫標記爲 SUSPECT。有關詳細信息,請參閱 SQL Server 錯誤日誌。 消息 5069,級別 16,狀態 1,第 1 行 ALTER DATABASE 語句失敗。 消息 5125,級別 24,狀態 2,第 1 行 文件 'E:\DataBase\sss.mdf' 彷佛已被操做系統截斷。其大小應爲 3072 KB,但實際大小爲 3064 KB。 消息 3414,級別 21,狀態 1,第 1 行 恢復期間出 錯,致使數據庫 'sss' (數據庫 ID 8)沒法從新啓動。請診斷並糾正這些恢復錯誤,或者從已知的正確備份中還原。若是沒法更正錯誤,或者爲意外錯誤,請與技術支持人員聯繫。
分享某位牛人的代碼
不管系統表、DMV、用戶表、系統存儲過程、系統視圖均可以讀取出來
附加到SQLSERVER以後,查看數據庫屬性,做者使用的是微軟的標準示例數據庫adventureworkoltp來作的測試
這幾個庫的版本號是611,655,661,706
咱們能夠本身新建一個數據庫,而後測試一下
牛人的博客地址:http://improve.dk/
項目代碼已經放上去GITHUB:https://github.com/improvedk/OrcaMDF
分享SQLSERVER技術內幕系列圖書筆記
本人把一些SQLSERVER技術內幕讀書筆記分享出來,其實也不算是分享,由於這些筆記一直躺在個人博客裏
你們能夠對我作的筆記進行搜索,技術內幕系列圖書最大的一個特徵是 :Microsoft Press 權威性不可忽視
筆記地址
《Microsoft SQL Server 6.5 技術內幕 筆記》
《Microsoft SQL Server 2005技術內幕:T-SQL查詢筆記》
《Microsoft SQL Server 2005技術內幕:存儲引擎筆記》
《Microsoft SQL Server 2005技術內幕 查詢、調整和優化筆記》
《Microsoft SQL Server 2005技術內幕: T-SQ程序設計 筆記》
《Microsoft SQL Server 2008技術內幕:T-SQL查詢 筆記》
《MICROSOFT SQL SERVER 2008技術內幕:T-SQL語言基礎 筆記》
EXEC sys.[sp_helpconstraint] @objname = N'[dbo].[nums]', -- nvarchar(776) @nomsg = '' -- varchar(5) select object_id, type, name,[parent_object_id] from sys.objects where parent_object_id = OBJECT_ID('customer') and type in ('C ','PK','UQ','F ', 'D ') -- ONLY 6.5 sysconstraints objects
《SQLSERVER2005存儲引擎》
SQLSERVER2005存儲引擎裏面把cachestore翻譯爲 存儲倉庫
還有SQLOS
SQLSERVER的工做線程是映射到Windows的線程池,SQLSERVER每條工做線程內存的分配都是由Windows來分配
SQL2005引入SQLOS,開始由SQLSERVER本身來調度線程,而先前是由Windows來調度
《SQLSERVER2005: T-SQ程序設計》
人們老是說遊標性能很差,這本書裏面解釋了遊標實際上也有他的優點的地方
《SQLSERVER2008 TSQL查詢》
《深刻解析SQLSERVER2008 》
DBCC的工做原理解釋得很清楚,大部份內容跟《SQLSERVER2005存儲引擎》有重疊
在最後一章DBCC 揭祕,譯者把鬼影記錄翻譯爲備份記錄,搞到一頭霧水
一邊看書,一邊思考
例如這篇文章《大表分批刪除腳本》 做者爲何要寫 DELETE TOP (5000) 呢? 有多是5000行鎖升級到表鎖的緣由
還有這一篇文章《恢復SQLSERVER被誤刪除的數據》
你們看完書本以後,看一下存儲過程的代碼,本身是否理解裏面代碼的意思
結尾
解剖SQL Server系列目錄
解剖SQLSERVER 第四篇 OrcaMDF裏對dates類型數據的解析(譯)
解剖SQLSERVER 第五篇 OrcaMDF裏讀取Bits類型數據(譯)
解剖SQLSERVER 第六篇 對OrcaMDF的系統測試裏避免regressions (譯)
解剖SQLSERVER 第七篇 OrcaMDF 特性概述(譯)
解剖SQLSERVER 第八篇 OrcaMDF 如今支持多數據文件的數據庫(譯)
解剖SQLSERVER 第九篇 OrcaMDF如今能經過系統DMVs顯示元數據(譯)
解剖SQLSERVER 第十篇 OrcaMDF Studio 發佈+ 特性重溫(譯)
解剖SQLSERVER 第十一篇 對SQLSERVER的多個版本進行自動化測試(譯)
解剖SQLSERVER 第十二篇 OrcaMDF 行壓縮支持(譯)
解剖SQLSERVER 第十三篇 Integers在行壓縮和頁壓縮裏的存儲格式揭祕(譯)
解剖SQLSERVER 第十四篇 Vardecimals 存儲格式揭祕(譯)
解剖SQLSERVER 第十五篇 SQLSERVER存儲過程的源文本存放在哪裏?(譯)
解剖SQLSERVER 第十六篇 OrcaMDF RawDatabase --MDF文件的瑞士軍刀(譯)
解剖SQLSERVER 第十七篇 使用 OrcaMDF Corruptor 故意損壞數據庫(譯)
解剖SQLSERVER 完結篇 關於Internals Viewer源代碼
因爲本人精力有限並且E文水平不太好,翻譯過程可能有錯漏,望你們見諒
建議先看一下技術內幕的書,不然一頭栽進代碼你會理解不了
經過閱讀這些譯文你們可能會以爲SQLSERVER的新存儲格式比較複雜,要遇上SQLSERVER的步伐不太容易,
改天微軟推出一個SQLSERVER補丁包,並在補丁包裏面添加新的存儲格式你的軟件可能又要歇菜了~
而我寫這系列文章並非要與這些數據庫恢復軟件商做對,而是讓你們知道他們可以恢復哪些數據,有哪些數據超出了他們的數據恢復能力
Mark S. Rasmussen的PPT: