刪庫時,我后悔沒早學(xué)會的數(shù)據(jù)庫知識
曾經(jīng)有一份真摯的數(shù)據(jù)庫知識擺在我的面前,我沒有珍惜,等到刪庫時才后悔莫及。人世間最悲痛的事莫過于此。如果再給我一次重來的機會,我一定會好好讀這篇文章,并把它收藏、分享給有需要的人。
大多數(shù)計算機系統(tǒng)都是有狀態(tài)的,并且可能會依賴存儲系統(tǒng)。隨著時間的推移,我對數(shù)據(jù)庫的了解程度不斷加深,這是以我們的設(shè)計錯誤導(dǎo)致數(shù)據(jù)丟失和中斷為代價。在數(shù)據(jù)量很大的系統(tǒng)中,數(shù)據(jù)庫是系統(tǒng)設(shè)計目標(biāo)的核心。盡管開發(fā)人員不可能對數(shù)據(jù)庫一無所知,但他們所預(yù)見和所經(jīng)歷的問題往往只是冰山一角。在本文中,我將分享一些見解,這些見解對于不擅長數(shù)據(jù)庫領(lǐng)域的開發(fā)人員來說非常有用。
如果在 99.999% 的時間里網(wǎng)絡(luò)不出問題,那你很幸運
現(xiàn)如今,一方面人們認(rèn)為網(wǎng)絡(luò)很可靠,一方面由于網(wǎng)絡(luò)中斷而導(dǎo)致系統(tǒng)宕機的情況卻又很普遍。這方面的研究工作并不多,而且通常由大公司主導(dǎo),而這些公司使用了配備定制硬件的專用網(wǎng)絡(luò)和專門的工作人員。
谷歌服務(wù)的可用性為 99.999%,他們聲稱只有 7.6% 的 Spanner(谷歌的分布式數(shù)據(jù)庫) 問題是因為網(wǎng)絡(luò)導(dǎo)致的,盡管他們一直認(rèn)為專用網(wǎng)絡(luò)是其可用性背后的核心支撐。2014 年,Bailis 和 Kingsbury 的一份調(diào)查報告對 Peter Deutsch 在 1994 年提出的分布式計算謬論之一提出了挑戰(zhàn)——網(wǎng)絡(luò)真的可靠嗎?
我們無法進行全面的調(diào)查,供應(yīng)商們也不會提供足夠的數(shù)據(jù)來說明有多少客戶的問題是因為網(wǎng)絡(luò)導(dǎo)致的。我們經(jīng)常會遭遇大型云供應(yīng)商網(wǎng)絡(luò)發(fā)生宕機,導(dǎo)致部分網(wǎng)絡(luò)癱瘓數(shù)小時,這些事件有大量可見的受影響客戶,還有很多是我們看不到的。網(wǎng)絡(luò)中斷可能會影響到更多方面,盡管并非所有事件都產(chǎn)生了很大的影響。云計算客戶也不一定能看到這些問題所在。當(dāng)問題出現(xiàn)時,他們不太可能認(rèn)為與供應(yīng)商的網(wǎng)絡(luò)錯誤有關(guān)。對他們來說,第三方服務(wù)就是黑盒。如果你不是供應(yīng)商,要估計出真實的影響程度是不太可能的。
與供應(yīng)商的報告相比,如果你的系統(tǒng)只有一小部分宕機與網(wǎng)絡(luò)問題有關(guān),那你是幸運的。網(wǎng)絡(luò)仍然受傳統(tǒng)問題的影響,比如硬件故障、拓?fù)渥兏?、管理配置變更和電源故障。但最近我才知道,一些新發(fā)現(xiàn)的問題(比如鯊魚咬斷海底光纜)也成了主要影響因素。
ACID 沒有表面看上去的那么簡單
ACID 代表原子性、一致性、隔離性和持久性。即使在發(fā)生崩潰、錯誤、硬件故障等類似事件時,數(shù)據(jù)庫也需要保證這些屬性是有效的。大多數(shù)關(guān)系型事務(wù)數(shù)據(jù)庫都盡量提供 ACID 保證,但很多 NoSQL 數(shù)據(jù)庫是沒有 ACID 事務(wù)保證的,因為實現(xiàn)成本很高。
在我剛進入這個行業(yè)時,我們的技術(shù)主管懷疑 ACID 是不是一個過時的概念??梢哉f,ACID 被認(rèn)為是一個種泛泛而談的概念,而不是一個嚴(yán)格的執(zhí)行標(biāo)準(zhǔn)?,F(xiàn)在,我發(fā)現(xiàn)它非常有用,因為它提供了一類問題和一類潛在的解決方案。
并不是每個數(shù)據(jù)庫都兼容 ACID,而且在兼容 ACID 的數(shù)據(jù)庫當(dāng)中,對 ACID 的解釋也可能存在差異。之所以存在差異,其中一個原因是在實現(xiàn) ACID 時涉及的權(quán)衡程度的不同。數(shù)據(jù)庫可能宣稱自己兼容 ACID,但對于一些邊緣情況,或者在面對“不太可能”出現(xiàn)的問題時,處理方式有所不同。
MongoDB 的 ACID 表現(xiàn)一直飽受爭議,即使是在發(fā)布了 v4 版本之后。MongoDB 在很長一段時間內(nèi)都不支持日志記錄。對于下面這種情況,應(yīng)用程序進行了兩次寫操作 (w1 和 w2),MongoDB 能夠持久化 w1,但因為發(fā)生硬件故障,導(dǎo)致無法持久化 w2。
MongoDB 在將數(shù)據(jù)寫入物理磁盤之前發(fā)生崩潰,造成數(shù)據(jù)丟失
將數(shù)據(jù)提交到磁盤是一個開銷很大的過程,它們聲稱寫入性能良好,卻是以避免頻繁提交數(shù)據(jù)為代價,從而犧牲了持久性?,F(xiàn)在,MongoDB 有了日志記錄,但臟寫仍然會影響數(shù)據(jù)的持久性,因為默認(rèn)情況下每 100 毫秒才提交一次日志。即使風(fēng)險大大降低,日記記錄的持久性和變更仍然有可能出現(xiàn)同樣的問題。
不同的數(shù)據(jù)庫有不同的一致性和隔離能力
在 ACID 這幾個屬性中,一致性和隔離級別的實現(xiàn)方式是最多的,因為權(quán)衡范圍最大。為了保持?jǐn)?shù)據(jù)一致性,數(shù)據(jù)庫需要進行協(xié)調(diào),爭用資源的情況會增加。當(dāng)需要在多個數(shù)據(jù)中心之間進行水平伸縮時 (特別是在不同的地理區(qū)域之間),就變得非常困難。隨著可用性的降低和網(wǎng)絡(luò)分區(qū)的頻繁出現(xiàn),提供高水平的一致性是非常困難的。關(guān)于這一問題的深入解釋,請參見 CAP 定理。不過需要注意的是,應(yīng)用程序可以在數(shù)據(jù)一致性方面做一些處理,或者程序員可能對這個問題有足夠的了解,可以在應(yīng)用程序中添加額外的邏輯來處理,而不是嚴(yán)重依賴數(shù)據(jù)庫。
數(shù)據(jù)庫通常會提供各種隔離級別,應(yīng)用程序開發(fā)人員可以根據(jù)權(quán)衡選擇最經(jīng)濟有效的隔離級別。較弱的隔離級別可能速度更快,但可能會引入數(shù)據(jù)競態(tài)問題。更強的隔離級別消除了一些潛在的數(shù)據(jù)競態(tài)問題,但速度較慢,并且可能會引入資源爭用,使數(shù)據(jù)庫慢到宕機。
現(xiàn)有并發(fā)模型及其之間關(guān)系的概覽
SQL 標(biāo)準(zhǔn)只定義了 4 個隔離級別,盡管在理論方面和實際當(dāng)中都還有更多可用的級別。如果你想進一步了解,jepson.io 提供了更多對現(xiàn)有并發(fā)模型的介紹。谷歌的 Spanner 保證了時鐘同步的外部串行性,即使這是一個更嚴(yán)格的隔離級別,但它在標(biāo)準(zhǔn)隔離級別中并沒有定義。
SQL 標(biāo)準(zhǔn)中提到的隔離級別是:
- 串行化 (最嚴(yán)格、成本最高):串行化執(zhí)行的效果與事務(wù)的串行執(zhí)行是一樣的。串行執(zhí)行是指每個事務(wù)在下一個事務(wù)開始之前執(zhí)行完成。需要注意的是,由于在解釋上的差異,串行化通常被實現(xiàn)成“快照隔離”(例如 Oracle),但在 SQL 標(biāo)準(zhǔn)中并沒有“快照隔離”。
- 可重復(fù)讀:當(dāng)前事務(wù)中未提交的讀取對當(dāng)前事務(wù)可見,但其他事務(wù)所做的更改 (如新插入的行) 不可見。
- 讀已提交:未提交的讀取對事務(wù)不可見。只有提交的寫是可見的,但可能會發(fā)生幻讀取。如果另一個事務(wù)插入和提交新行,當(dāng)前事務(wù)在查詢時可以看到它們。
- 讀未提交 (最不嚴(yán)格、成本最低):允許臟讀,事務(wù)可以看到其他事務(wù)未提交的更改。實際上,這個級別對于返回近似聚合很有用,比如 COUNT(*) 查詢。
串行化級別將發(fā)生數(shù)據(jù)競爭的機會降到最低,盡管它的開銷最大,并給系統(tǒng)帶來了最多的爭用。其他隔離級別開銷較小,但增加了數(shù)據(jù)競爭的可能性。有些數(shù)據(jù)庫允許設(shè)置隔離級別,有些數(shù)據(jù)庫不一定支持所有的隔離級別。
各種數(shù)據(jù)庫對隔離級別的支持情況
使用樂觀鎖
使用數(shù)據(jù)庫鎖的成本是非常高的,它們不僅引入了更多的爭用,而且要求應(yīng)用程序服務(wù)器和數(shù)據(jù)庫之間保持穩(wěn)定的連接。排它鎖受網(wǎng)絡(luò)分區(qū)的影響更大,并會導(dǎo)致難以識別和解決的死鎖。在這種情況下,可以考慮使用樂觀鎖。
樂觀鎖是指在讀取一行數(shù)據(jù)時,記下它的版本號、最近修改的時間戳或校驗和。然后,你可以在修改記錄之前檢查版本有沒有發(fā)生變化。
- UPDATE products
- SET name = 'Telegraph receiver', version = 2
- WHERE id = 1 AND version = 1
如果之前有一個更新操作修改了 products 表,那么當(dāng)前的更新操作將不修改任何數(shù)據(jù)。如果之前沒有被修改,當(dāng)前的更新操作將修改一行數(shù)據(jù)。
除了臟讀和數(shù)據(jù)丟失之外,還有其他異常
在討論數(shù)據(jù)一致性時,我們主要關(guān)注可能會導(dǎo)致臟讀和數(shù)據(jù)丟失的競態(tài)條件。但除了這些,我們還要注意異常數(shù)據(jù)。
這類異常的一個例子是寫傾斜(write skew)。寫傾斜并不是在進行寫操作時發(fā)生臟讀或數(shù)據(jù)丟失時出現(xiàn)的,而是在數(shù)據(jù)的邏輯約束被破壞時出現(xiàn)的。
例如,假設(shè)有一個監(jiān)控應(yīng)用程序,要求至少有一個運維人員可以隨叫隨到。
對于上述情況,如果兩個事務(wù)成功提交,就會出現(xiàn)寫傾斜。即使沒有發(fā)生臟讀或數(shù)據(jù)丟失,數(shù)據(jù)的完整性也會丟失,因為有兩個人被指派隨叫隨到。
串行化化隔離級別、模式設(shè)計或數(shù)據(jù)庫約束可能有助于消除寫傾斜。開發(fā)人員需要在開發(fā)期間識別出這些異常,避免在生產(chǎn)環(huán)境中出現(xiàn)這個問題。話雖如此,直接從代碼中識別出寫傾斜是非常困難的。特別是在大型的系統(tǒng)中,如果不同的團隊使用相同的表,但沒有相互溝通,也沒有檢查如何訪問數(shù)據(jù),就更難發(fā)現(xiàn)問題了。
順序問題
數(shù)據(jù)庫提供的核心功能之一是順序保證,但這也是讓應(yīng)用程序開發(fā)人員感到驚訝的一個地方。數(shù)據(jù)庫按照接收事務(wù)的順序來安排順序,而不是按照代碼中所寫的事務(wù)順序來安排順序。事務(wù)執(zhí)行的順序很難預(yù)測,特別是在大規(guī)模并發(fā)系統(tǒng)中。
在開發(fā)過程中,特別是在使用非阻塞開發(fā)庫時,糟糕的可讀性可能會導(dǎo)致出現(xiàn)這樣的問題:用戶認(rèn)為事務(wù)是按順序執(zhí)行的,但事務(wù)可能以任意順序到達(dá)數(shù)據(jù)庫。下面的代碼看起來像是要順序地調(diào)用 T1 和 T2,但如果這些函數(shù)是非阻塞的,并且會立即返回 promise,那么實際的調(diào)用順序?qū)⒂伤鼈兊竭_(dá)數(shù)據(jù)庫的時間決定。
- result1 = T1() // 返回的是promise
- result2 = T2()
如果原子性是必需的 (完全提交或中止所有操作),而且順序很重要,那么 T1 和 T2 應(yīng)該包含在單個數(shù)據(jù)庫事務(wù)中。
應(yīng)用程序級別的分片可在應(yīng)用程序之外進行
分片是對數(shù)據(jù)庫進行水平分區(qū)的一種方法。盡管有些數(shù)據(jù)庫可以自動對數(shù)據(jù)進行水平分區(qū),但有些數(shù)據(jù)庫不會這么做,或者可能不擅長這么做。當(dāng)數(shù)據(jù)架構(gòu)師或開發(fā)人員能夠預(yù)測數(shù)據(jù)的訪問模式時,他們可能會在用戶端進行水平分區(qū),而不是在數(shù)據(jù)庫端,這叫作應(yīng)用程序級別的分片。
“應(yīng)用程序級別的分片”通常給人一種錯誤的印象,即認(rèn)為分片應(yīng)該存在于應(yīng)用程序中。實際上,分片功能可以作為數(shù)據(jù)庫前面的一個層。隨著數(shù)據(jù)增長和模式的迭代,分片需求可能會變得越來越復(fù)雜。
一個應(yīng)用服務(wù)器與分片服務(wù)分離的示例架構(gòu)
將分片作為單獨的服務(wù),可以在不重新部署應(yīng)用程序的前提下提升分片策略的迭代能力。Vitess 是這方面的一個很好的例子。Vitess 為 MySQL 提供了水平分片能力,客戶端可以通過 MySQL 協(xié)議連接到 Vitess,Vitess 會在各個 MySQL 節(jié)點上對數(shù)據(jù)進行分片。
https://youtu.be/OCS45iy5v1M?t=204
自動遞增 ID 有“毒”
自動遞增是生成主鍵的常用方法。使用數(shù)據(jù)庫作為 ID 生成器,并在數(shù)據(jù)庫中創(chuàng)建帶有 ID 生成的表,這種情況并不少見。但是,通過自動遞增生成主鍵可能不是理想的方法,原因如下:
- 在分布式數(shù)據(jù)庫系統(tǒng)中,自動遞增是一個難題。你需要一個全局鎖來生成 ID,但如果可以生成 UUID,就不需要協(xié)調(diào)數(shù)據(jù)庫節(jié)點。使用帶鎖的自動遞增可能會引入爭用,并且可能會顯著降低分布式寫入性能。像 MySQL 這樣的數(shù)據(jù)庫可能需要特定的配置,并且要保證主主復(fù)制的正確性。但是,配置很容易出錯,并可能導(dǎo)致寫入中斷。
- 一些數(shù)據(jù)庫有基于主鍵的分區(qū)算法。順序 ID 可能會導(dǎo)致不可預(yù)測的熱點,導(dǎo)致某些分區(qū)數(shù)據(jù)量過大,而其他分區(qū)處于空閑狀態(tài)。
- 訪問數(shù)據(jù)庫最快方法是使用主鍵。如果你使用了其他列來標(biāo)識記錄,那么順序 ID 可能會變得毫無意義。所以,請盡可能選擇一個全局唯一的自然主鍵 (例如用戶名)。
在決定哪種方法更適合自己之前,請考慮一下自動遞增 ID 與 UUID 對索引、分區(qū)和分片的影響。
無鎖的陳舊數(shù)據(jù)很有用
多版本并發(fā)控制 (MVCC) 可以支持上述的很多一致性方面的能力。一些數(shù)據(jù)庫 (如 Postgres、Spanner) 借助 MVCC 讓每個事務(wù)可以查看快照,即數(shù)據(jù)庫的舊版本。這些事務(wù)可以串行化,以此來保持一致性。從舊快照讀取數(shù)據(jù)時,讀取的是陳舊的數(shù)據(jù)。
讀取稍微陳舊一點的數(shù)據(jù)也是很有用的,例如,基于數(shù)據(jù)生成分析報告或計算近似聚合值。
讀取陳舊數(shù)據(jù)的第一個好處是延時 (特別是當(dāng)數(shù)據(jù)庫分布在不同的地理區(qū)域時)。MVCC 數(shù)據(jù)庫的第二個優(yōu)點是它允許只讀事務(wù)是無鎖的。如果讀取陳舊數(shù)據(jù)是可接受的,那么對于偏重讀取很大的應(yīng)用程序來說,這就是一個主要的優(yōu)點。
應(yīng)用服務(wù)器從本地副本讀取 5 秒前的陳舊數(shù)據(jù),即使在太平洋的另一端有可用的最新版本
數(shù)據(jù)庫會自動清除舊版本,在某些情況下,它們允許按需進行清理。例如,Postgres 允許用戶按需清理,或者每隔一段時間自動清理一次,而 Spanner 則使用垃圾回收器來清除超過一小時的陳舊數(shù)據(jù)。
任何與時鐘有關(guān)的資源之間都會發(fā)生時鐘傾斜
計算系統(tǒng)最隱秘的秘密是所有的時間 API 都會“撒謊”。計算機無法準(zhǔn)確地知道當(dāng)前時間,它們都有一個石英晶體,會產(chǎn)生計時信號,但石英晶體無法準(zhǔn)確地計時,不是比實際時鐘快就是比實際時鐘慢。每天出現(xiàn)的時間漂移最多可長達(dá) 20 秒。為了準(zhǔn)確起見,計算機上的時間需要時不時地與實際時間同步。
NTP 服務(wù)器用于同步時間,但同步本身可能會因為網(wǎng)絡(luò)而出現(xiàn)延遲。在同一個數(shù)據(jù)中心中進行 NTP 服務(wù)器同步需要花費一點時間,而與公共 NTP 服務(wù)器同步有可能出現(xiàn)更大的傾斜。
原子時鐘和 GPS 時鐘是用來確定當(dāng)前時間更好的一種來源,但它們昂貴,而且需要復(fù)雜的設(shè)置,無法在每臺機器上安裝??紤]到這些限制,數(shù)據(jù)中心使用了多層方法。雖然原子時鐘和 GPS 時鐘提供了準(zhǔn)確的時間,但它們的時間是通過輔助服務(wù)器廣播到其他的機器上的。這意味著每臺機器都會與實際的時間發(fā)生某種量級的傾斜。
應(yīng)用程序和數(shù)據(jù)庫通常位于不同的機器上,不僅分布在多臺機器上的數(shù)據(jù)庫節(jié)點無法就時間達(dá)成一致,應(yīng)用服務(wù)器時鐘和數(shù)據(jù)庫節(jié)點時鐘也無法達(dá)成一致。
谷歌的 TrueTime 采用了不同的方法。大多數(shù)人認(rèn)為谷歌在時鐘方面的進步要歸功于他們使用了原子時鐘和 GPS 時鐘,但這只是其中的部分原因。TrueTime 實際上做了這些事情:
- TrueTime 使用兩種不同的來源:GPS 和原子時鐘。這些時鐘有不同的故障模式,因此同時使用它們提高了可靠性。
- TrueTime 有一個非常規(guī)的 API,它以間隔的形式返回時間,時間可以是下限和上限之間的任意點。谷歌的分布式數(shù)據(jù)庫 Spanner 可以等待,直到確定當(dāng)前時間超過了特定時間。
Spanner 組件使用了 TrueTime,TT.now() 返回一個時間間隔,Spanner 可以進行 sleep,以確保當(dāng)前時間已經(jīng)通過了一個特定的時間戳。
延遲沒有看上去的那么簡單
如果你在一個房間里問 10 個人“延遲”是什么意思,他們可能會有不同的答案。在數(shù)據(jù)庫中,延遲通常是指“數(shù)據(jù)庫延遲”,而不是客戶端所感知到的延遲??蛻舳丝梢钥吹綌?shù)據(jù)庫的延遲和網(wǎng)絡(luò)延遲。在調(diào)試問題時,能夠識別客戶端延遲和數(shù)據(jù)庫延遲是非常重要的。在收集和顯示指標(biāo)時,始終都要考慮到兩者。
評估每個事務(wù)的性能需求
有時候,數(shù)據(jù)庫會說明它們在讀寫吞吐量和延遲方面的性能特征和限制。但在評估數(shù)據(jù)庫性能時,更全面的做法是對每一個關(guān)鍵操作 (查詢或事務(wù)) 進行評估。例如:
- 往一張表 X(已經(jīng)有 5 千萬行記錄)插入新行,并更新相關(guān)表,此時的寫入吞吐量和延遲是怎么樣的?
- 當(dāng)平均朋友數(shù)量為 500 人時,查詢某個用戶的朋友的朋友,此時的延遲是怎樣的?
- 當(dāng)用戶訂閱了 500 個帳號 (每小時有 X 項更新) 時,查詢用戶時間軸的前 100 條記錄,此時的延遲是怎樣的?
性能評估可能包含了這些情況,直到你確信數(shù)據(jù)庫能夠滿足你的性能需求為止。
在收集指標(biāo)時,要小心高基數(shù)。如果你需要高基數(shù)調(diào)試數(shù)據(jù),請使用日志,甚至是分布式跟蹤信息。
嵌套事務(wù)有風(fēng)險
并不是每一種數(shù)據(jù)庫都支持嵌套事務(wù)。嵌套事務(wù)可能會導(dǎo)致意外的編程錯誤,這些錯誤不容易識別,直到拋出異常。
嵌套事務(wù)可以在客戶端檢測和避免。如果無法避免,就要注意避免出現(xiàn)意外情況,即已提交的事務(wù)由于子事務(wù)而意外中止。
在不同的層封裝事務(wù)可能會出現(xiàn)意外的嵌套事務(wù),而從可讀性角度來看,可能很難理解其意圖??纯聪旅孢@個例子:
- with newTransaction():
- Accounts.create("609-543-222")
- with newTransaction():
- Accounts.create("775-988-322")
- throw Rollback();
這段代碼的結(jié)果是什么?它是回滾兩個事務(wù)還是只回滾內(nèi)部事務(wù)?如果我們使用多層庫來封裝事務(wù),會發(fā)生什么呢?我們是否能夠識別并改善這種情況?
假設(shè)一個數(shù)據(jù)層已經(jīng)在一個事務(wù)中實現(xiàn)了多個操作 (例如 newAccount),在業(yè)務(wù)邏輯的事務(wù)中運行它們時會發(fā)生什么?此時具有怎樣的隔離性和一致性特征?
- function newAccount(id string) {
- with newTransaction():
- Accounts.create(id)
- }
與其要處理這種問題,不如避免使用嵌套事務(wù)。數(shù)據(jù)層仍然可以實現(xiàn)自己的操作,但無需創(chuàng)建事務(wù)。然后,業(yè)務(wù)邏輯可以啟動、執(zhí)行、提交或中止事務(wù)。
- function newAccount(id string) {
- Accounts.create(id)
- }
- // 在主程序中:
- with newTransaction():
- // 從數(shù)據(jù)庫讀取一些配置數(shù)據(jù)
- // 調(diào)用ID服務(wù)生成ID
- Accounts.create(id)
- Uploads.create(id) // 創(chuàng)建用戶上傳隊列
事務(wù)不應(yīng)該依賴應(yīng)用程序狀態(tài)
應(yīng)用程序開發(fā)人員可能會在事務(wù)中使用應(yīng)用程序狀態(tài)來更新某些值或設(shè)置查詢參數(shù),這個時候要注意作用域。當(dāng)發(fā)生網(wǎng)絡(luò)問題時,客戶端經(jīng)常會重試事務(wù)。如果事務(wù)依賴的狀態(tài)在其他地方被修改,就使用了錯誤的值。
- var seq int64
- with newTransaction():
- newSeq := atomic.Increment(&seq)
- Entries.query(newSeq)
- // 其他操作
無論最終結(jié)果如何,上面的事務(wù)每次運行時都會增加序列號。如果由于網(wǎng)絡(luò)原因提交失敗,在第二次重試時,它將使用不同的序列號進行查詢。
查詢計劃的作用
查詢計劃決定了數(shù)據(jù)庫將會如何執(zhí)行查詢。它們還會在執(zhí)行查詢之前對其進行分析和優(yōu)化。查詢計劃只能根據(jù)某些信號提供一些可能的估計。例如下面這個查詢:
- SELECT * FROM articles where author = "rakyll" order by title;
獲取結(jié)果有兩種方法:
- 全表掃描:我們可以遍歷表中的每條記錄,并返回與作者姓名匹配的文章,然后根據(jù)標(biāo)題排序。
- 索引掃描:我們可以使用一個索引來查找匹配的 ID,獲取這些行,然后排序。
查詢計劃的作用是確定最佳執(zhí)行策略。但可用于預(yù)測的信號是有限的,因此可能會導(dǎo)致做出錯誤的決策。DBA 或開發(fā)人員可以用它們來診斷和調(diào)優(yōu)性能較差的查詢。慢查詢?nèi)罩?、延遲問題或執(zhí)行時間統(tǒng)計信息可用于識別需要優(yōu)化的查詢。
查詢計劃提供的一些度量可能不會很準(zhǔn)確,特別是在估計延遲或 CPU 時間方面。作為查詢計劃的補充,跟蹤和執(zhí)行路徑工具在診斷這些問題方面更有用,但并不是每種數(shù)據(jù)庫都會提供這些工具。
在線遷移雖復(fù)雜,但還是有跡可循
在線或?qū)崟r遷移就是在不停機、不影響數(shù)據(jù)正確性的情況下從一個數(shù)據(jù)庫遷移到另一個數(shù)據(jù)庫。如果要遷移到同一個數(shù)據(jù)庫或引擎,實時遷移會容易一些,但要遷移到具有不同性能特征和模式需求的新數(shù)據(jù)庫,就要復(fù)雜得多。
在線遷移有一些可遵循的模式:
- 在兩個數(shù)據(jù)庫上執(zhí)行雙重寫操作。在這個階段,新的數(shù)據(jù)庫不包含所有數(shù)據(jù),但會包含新數(shù)據(jù)。在這一步穩(wěn)妥之后,就可以進入第二步。啟用針對兩個數(shù)據(jù)庫的查詢路徑。
- 讓新數(shù)據(jù)庫承擔(dān)主要的讀寫任務(wù)。
- 停止對舊數(shù)據(jù)庫的寫入,但可以繼續(xù)從舊數(shù)據(jù)庫讀取數(shù)據(jù)。此時,新數(shù)據(jù)庫仍然不包含所有數(shù)據(jù),要讀取舊數(shù)據(jù),仍然需要從舊數(shù)據(jù)庫獲得。
- 此時,舊數(shù)據(jù)庫是只讀的。用舊數(shù)據(jù)庫中的數(shù)據(jù)填充新數(shù)據(jù)庫缺失的數(shù)據(jù)。遷移完成后,所有讀寫路徑都可以使用新數(shù)據(jù)庫,舊數(shù)據(jù)庫可以從系統(tǒng)中移除。
數(shù)據(jù)庫規(guī)模增長帶來的不可預(yù)測性
數(shù)據(jù)庫的增長會帶來不可預(yù)測的伸縮性問題。
隨著數(shù)據(jù)庫的增長,之前對數(shù)據(jù)大小和網(wǎng)絡(luò)容量的假設(shè)或預(yù)期可能會過時,比如大型 scheme 重構(gòu)、大規(guī)模的運維改進、容量問題、部署計劃改變或遷移到其他數(shù)據(jù)庫以避免宕機。
不要以為了解數(shù)據(jù)庫的內(nèi)部結(jié)構(gòu)就足夠了,因為伸縮性會帶來新的未知問題。不可預(yù)測的數(shù)據(jù)熱點、不均勻的數(shù)據(jù)分布、意外的容量和硬件問題、不斷增長的流量和新的網(wǎng)絡(luò)分區(qū),這些都會迫使你重新考慮數(shù)據(jù)庫、數(shù)據(jù)模型、部署模型和部署規(guī)模。