美女程式設計師分享資料庫常見17個使用誤區 - Jaana
絕大多數計算機系統都具有某種狀態,並且可能依賴於儲存系統。我對資料庫的瞭解是隨著時間的推移而積累的,但是在此過程中,我們的設計錯誤導致了資料丟失和中斷。在資料繁重的系統中,資料庫是系統設計目標和折衷方案的核心。
在本系列中,我將分享一些我特別發現的見解,這些見解對那些不擅長該領域的開發人員非常有用。
- 如果99.999%的時間網路都不成問題,您將很幸運。
- ACID有很多含義。
- 每個資料庫具有不同的一致性和隔離功能。
- 當您無法持有鎖時,可以使用樂觀鎖。
- 除了髒讀和資料丟失外,還有其他異常。
- 我和我的資料庫並不總是就順序處理達成一致。
- 應用程式級分片可以存在於應用程式外部。
- 主鍵自動增加AUTOINCREMENT可能有害。
- 過時的資料可能有用且無鎖。
- 任何時鐘源之間都會發生時鐘偏斜。
- 延遲有很多含義。
- 評估每筆交易的效能要求。
- 巢狀交易可能有害。
- 事務不應維持應用程式狀態。
- 查詢計劃人員可以提供很多有關資料庫的知識。
- 線上遷移很複雜,但可能。
- 資料庫的顯著增長帶來了不可預測性。
簡要翻譯如下,詳細點選標題見原文:
1. 如果99.999%的時間網路都不成問題,您將很幸運。
如果網路問題僅佔導致中斷的潛在問題的一小部分,您就很幸運。網路仍然遭受常規問題的困擾,例如硬體故障,拓撲更改,管理配置更改和電源故障。
2. ACID有很多含義
ACID代表原子性,一致性,隔離性,耐用性。這些是資料庫事務需要確保其使用者有效性的屬性,即使發生崩潰,錯誤,硬體故障或類似情況也是如此。如果沒有ACID或類似的合同,應用程式開發人員將無法確定他們的職責以及資料庫提供的內容。大多數關係事務資料庫都試圖符合ACID,但是諸如NoSQL運動之類的新方法催生了許多沒有ACID事務的資料庫,因為它們實現起來很昂貴。
當我剛接觸該行業時,我們的技術主管在爭論ACID是否已過時。可以說ACID被認為是寬鬆的描述,而不是嚴格的實施標準。今天,我發現它最有用,因為它提供了一類問題(以及一類可能的解決方案)。
並非每個資料庫都符合ACID,並且在符合ACID的資料庫中,ACID的解釋可能有所不同。實施ACID的方式不同的原因之一是實施ACID功能涉及的權衡數量。資料庫可能將自己宣傳為ACID,但在邊緣情況或它們如何處理“不太可能”的事件時可能仍具有不同的解釋。開發人員至少可以從高層次上學習資料庫如何實現事物,以便對故障模式和設計權衡有一個正確的瞭解。
MongoDB已經擁有日誌功能,但是髒寫入仍然會影響資料的永續性,因為預設情況下它們每100毫秒提交一次日記。即使風險顯著降低,對於日誌的永續性以及這些日誌中表示的更改,仍然可能存在相同的情況。
3. 每個資料庫具有不同的一致性和隔離功能。
在ACID屬性中,一致性和隔離性具有最大的不同實現細節範圍,因為折衷範圍更廣。一致性和隔離性是昂貴的功能。他們需要協調並正在增加爭用以保持資料的一致性。當必須在資料中心之間(尤其是在不同地理區域之間)進行水平擴充套件時,問題將變得更加困難。隨著可用性的降低和網路分割槽的發生越來越頻繁,提供高水平的一致性可能會非常困難。
CAP定理對此現象進行更一般的解釋。還值得一提的是,應用程式可以處理一些不一致的情況,否則程式設計師可能對問題有足夠的瞭解,可以在應用程式中新增其他邏輯來處理該問題,而無需過多依賴其資料庫。
資料庫通常提供各種隔離層,因此應用程式開發人員可以根據自身的權衡取捨最具成本效益的層。較弱的隔離可能會更快,但可能會導致資料爭用。更強的隔離消除了一些潛在的資料爭用,但是會更慢,並且可能引入爭用,從而使資料庫減慢到可能導致中斷的程度。
上圖是現有併發模型及其之間的關係的概述。
即使在理論上和實踐上還有更多的隔離級別,SQL標準也只定義了四個隔離級別。如果需要進一步閱讀,jepson.io提供了有關現有併發模型的引人注目的概述。例如,Google的Spanner通過時鐘同步來保證外部可序列化,即使這是一個嚴格的隔離層,也沒有在標準隔離層中定義。
SQL標準中提到的隔離級別為:
- 可序列化(最嚴格,最昂貴):可序列化的執行與那些事務的某些序列執行產生相同的效果。序列執行是指每個事務在下一個事務開始之前執行完成的過程。關於可序列化級別的一個注意事項是,由於解釋上的差異,通常將其實現為“快照隔離”(例如Oracle),並且SQL標準中未表示“快照隔離”。
- 可重複讀取:當前事務中未提交的讀取對於當前事務是可見的,但其他事務(例如新插入的行)所做的更改將不可見。
- 已提交讀:未提交的讀對事務不可見。只有已提交的寫入是可見的,但是幻像讀取可能會發生。如果另一個事務插入並提交新行,則當前事務在查詢時可以看到它們。
- 未提交讀(最不嚴格,便宜):允許進行髒讀,事務可以看到其他事務尚未提交的更改。實際上,此級別對於返回近似聚合(例如對錶的COUNT(*)查詢)很有用。
可序列化級別雖然成本最高且引入了最多的系統爭用,但卻使發生資料爭用的機會最少。其他隔離級別更便宜,但增加了資料爭用的可能性。有些資料庫允許您設定隔離級別,有些資料庫則對它們有更多的看法,而不一定支援所有資料庫。
即使資料庫宣傳其對這些隔離級別的支援,但仔細檢查它們的行為仍可能提供有關實際操作的更多見解。
Kleppmann的hermitage提供不同的併發異常的概述和資料庫是否能夠在特定的隔離級別來處理它。他的研究表明,資料庫設計師可以如何不同地解釋隔離級別。
4. 當您無法持有鎖時,可以使用樂觀鎖。
鎖不僅成本高昂,不僅因為它們會在資料庫中引入更多爭用,而且可能需要從應用程式伺服器到資料庫的一致連線。排他鎖可能會更嚴重地受到網路分割槽的影響,並導致難以識別和解決的死鎖。如果無法持有排他鎖,則可以選擇樂觀鎖。
樂觀鎖定是一種讀取行,記錄版本號,最近修改的時間戳或其校驗和的一種方法。然後,您可以在更改記錄之前檢查版本是否沒有原子更改。
UPDATE products SET name = 'Telegraph receiver', version = 2 WHERE id = 1 AND version = 1 |
如果另一個更新之前更改了該行,則對產品表的更新將影響0行。如果沒有更早的更新,它將影響1行,並且我們可以判斷我們的更新已成功。
5.除了髒讀和資料丟失外,還有其他異常。
當我們談論資料一致性時,我們主要關注可能導致競爭性讀取和資料丟失的競爭狀況。但是資料異常不僅限於此。
這種型別的異常的一個例子是寫偏斜。寫偏斜很難識別,因為我們沒有積極尋找它們。當寫操作發生髒讀或丟失時,不會造成寫偏斜,但會損害資料的邏輯約束。
例如,假設一個監視應用程式要求多個個操作者中的一個始終處於oncall狀態為真。
BEGIN tx1; BEGIN tx2; SELECT COUNT(*) FROM operators WHERE oncall = true; 0 SELECT COUNT(*) FROM operators WHERE oncall = TRUE; 0 UPDATE operators UPDATE operators SET oncall = TRUE SET oncall = TRUE WHERE userId = 4; WHERE userId = 2; COMMIT tx1; COMMIT tx2; |
在上述情況下,如果兩個事務成功提交,將存在寫偏斜。即使沒有發生髒讀或資料丟失的情況,資料的完整性也會丟失,因為指定了兩個oncall狀態設定為True。
可序列化的隔離,模式設計或資料庫約束可以幫助消除寫偏斜。開發人員必須能夠在開發過程中識別此類異常,以避免生產中出現資料異常。話雖如此,要識別程式碼庫中的寫偏斜可能非常困難。尤其是在大型系統中,如果不同的團隊負責基於相同的表構建要素,而又彼此不溝通並且不檢查他們如何訪問資料。
6.我和我的資料庫並不總是就順序處理達成一致。
資料庫提供的核心功能之一是順序保證,但是順序對於應用程式開發人員可能是令人驚訝的。資料庫按照事務接收的順序檢視事務,而不是按開發人員看到的程式設計順序檢視事務。事務執行的順序很難預測,尤其是在大量併發系統中。
在開發期間,尤其是在使用非阻塞庫時,較差的樣式和可讀性可能會導致以下問題:使用者認為事務可以順序執行,即使它們可以以任何順序到達資料庫。下面的程式使T1和T2看起來將被順序呼叫,但是如果這些函式是非阻塞的並且立即以承諾返回,則呼叫的順序將取決於它們在資料庫中收到的時間。
result1 = T1()//結果實際上是承諾promise result2 = T2() |
如果需要原子性(完全提交或中止所有操作)並且順序很重要,則T1和T2中的操作應在單個資料庫事務中執行。
7.應用程式級分片可以存在於應用程式外部。
片是一種對資料庫進行水平分割槽的方法。即使某些資料庫可以自動在水平方向上對資料進行分割槽,但有些資料庫不能或可能不擅長。當資料架構師/開發人員可以預測如何訪問資料時,他們可以在使用者區域建立水平分割槽,而不是將這項工作委託給他們的資料庫。這稱為應用程式級分片。
應用程式級分片的名稱經常給人以錯誤的印象,即分片應存在於應用程式服務中。分片功能可以實現為資料庫前面的一層。根據資料增長和架構迭代,分片需求可能會變得複雜。能夠在某些策略上進行迭代而不必重新部署應用程式伺服器可能會很有用。
將分片作為單獨的服務可以提高迭代分片策略的能力,而不必重新部署應用程式。應用程式級別分片系統的此類示例之一是Vitess。Vitess為MySQL提供水平分片,並允許客戶端通過MySQL協議連線到它,並且將資料分片到彼此不認識的各種MySQL節點上。
8.自動增加可能有害。
有幾個原因導致通過自動增量生成主鍵可能並不理想:
- 在分散式資料庫系統中,自動遞增是一個難題。需要全域性鎖才能生成ID。如果可以生成UUID,則不需要資料庫節點之間的任何協作。帶鎖的自動增量可能會引起競爭,並且可能會大大降低分散式情況下插入的效能。某些資料庫(例如MySQL)可能需要特定的配置,並且需要更多的注意才能使主-主複製正常進行。該配置很容易弄亂,並可能導致寫中斷。
- 一些資料庫具有基於主鍵的分割槽演算法。順序ID可能會導致無法預測的熱點,並且可能會使某些分割槽不堪重負,而另一些分割槽則保持空閒狀態。
- 訪問資料庫中行的最快方法是通過其主鍵。如果您有更好的方法來標識記錄,則順序ID可能會使表中最重要的列成為無意義的值。請儘可能選擇一個全球唯一的自然主鍵(例如,使用者名稱)。
在確定哪種方法更適合您之前,請考慮自動遞增的ID與UUID對索引,分割槽和分片的影響。
9.過時的資料可能有用且無鎖。
多版本併發控制(MVCC)支援我們上面簡要討論的許多一致性功能。一些資料庫(例如Postgres,Spanner)使用MVCC來允許每個事務檢視快照,這是資料庫的舊版本。快照的事務仍然可以序列化以保持一致性。從舊快照讀取時,您將讀取過時的資料。
讀取稍微陳舊的資料將很有用,例如,當您根據資料生成分析資料或計算近似的合計值時。
讀取陳舊資料的第一個優點是延遲(特別是如果您的資料庫分佈在不同的地理區域中)。MVCC資料庫的第二個優點是它將允許只讀事務成為無鎖的。如果可以忍受過時的資料,則是讀取大量應用程式中的主要優勢。
資料庫會自動清除舊版本,在某些情況下,它們允許您按需執行。例如,Postgres允許使用者VACUUM根據需要以及偶爾自動吸塵一次,而Spanner執行垃圾收集器以擺脫早於一個小時的版本。
10.任何時鐘源之間都會發生時鐘偏斜
計算中最隱祕的祕密是所有時間的API都是謊言。我們的機器無法準確知道當前時間。我們的計算機都包含一個石英晶體,該石英晶體會產生一個計時訊號。但是石英晶體無法準確地滴答和漂移時間,而不是比實際時鐘快或慢。每天漂移可能長達20秒。為了準確起見,我們的計算機上的時間需要不時地與實際時間同步。
NTP伺服器用於同步,但是同步本身可能由於網路而延遲。與同一資料中心中的NTP伺服器同步可能需要一些時間,與公用NTP伺服器同步可能會導致更多偏差。
原子鐘和GPS時鐘是確定當前時間的更好來源,但是它們昂貴,並且需要複雜的設定,因此無法將其安裝在每臺機器上。考慮到這些限制,在資料中心中,使用了多層方法。當原子和/或GPS時鐘提供準確的計時時,它們的時間會通過輔助伺服器廣播到其他機器。這意味著每臺機器都會在一定程度上偏離實際當前時間。
Google的TrueTime在這裡採用了不同的方法。大多數人認為Google在時鐘方面的進步可以歸功於他們對原子鐘和GPS時鐘的使用,但這只是故事的一部分。這是TrueTime的作用:
- TrueTime使用兩種不同的來源:GPS和原子鐘。這些時鐘具有不同的故障模式,因此同時使用它們可以提高可靠性。
- TrueTime具有非常規的API。它以時間間隔返回時間。時間實際上可以在下限和上限之間的任何位置。然後,Google的分散式資料庫Spanner可以等到確定當前時間已超出特定時間。這種方法給系統增加了一些等待時間,特別是當主機通告的不確定性很高時,即使在全域性分佈的情況下也可以提供正確性。
隨著對當前時間的信心下降,這意味著Spanner操作可能需要更多時間。這就是為什麼即使擁有精確的時鐘都是不可能的,但保持對效能的高信心仍然很重要。
11. 延遲有很多含義
如果您問一個房間中的十個人“延遲”是什麼意思,他們可能都有不同的答案。在資料庫中,延遲通常稱為“資料庫延遲”,而不是客戶端感知的延遲。客戶端將看到資料庫延遲和網路延遲的延遲。在除錯不斷升級的問題時,能夠識別客戶端和資料庫延遲至關重要。收集和顯示指標時,請始終考慮同時使用兩者。
12.評估每筆事務交易的效能要求
有時,資料庫會在寫入和讀取吞吐量以及延遲方面公佈其效能特徵和限制。儘管這可以從總體上概述主要的阻止因素,但是在評估新資料庫的效能時,更全面的方法是分別評估關鍵操作(每個查詢和/或每個事務)。例子:
- 在具有給定約束的表X中插入新行(具有5000萬行)並在相關表中填充行時,寫入吞吐量和延遲。
- 平均好友數為500時,查詢使用者好友的時延。
- 當使用者訂閱了每小時X個條目的500個帳戶時,檢索使用者時間軸的前100條記錄的延遲。
在您確信資料庫將能夠滿足您的效能要求之前,評估和試驗可能包含此類關鍵情況。在收集延遲指標和設定SLO時,類似的經驗法則也在考慮這種故障。
在收集每個操作的指標時,請注意高基數。如果需要高基數除錯資料,請使用日誌,甚至收集或分散式跟蹤。請參閱要除錯延遲嗎?有關延遲除錯方法的概述。
13.巢狀事務可能有害
並非每個資料庫都支援巢狀事務,但是當巢狀資料庫支援巢狀事務時,巢狀事務可能會導致令人驚訝的程式設計錯誤,這些錯誤通常很難被發現,除非您清楚看到異常。
如果您想避免巢狀事務,則客戶端庫可以進行工作來檢測和避免巢狀事務。如果無法避免,則必須注意避免出現意外情況,在這種情況下,已提交的事務由於子事務而意外中止。
將事務封裝在不同的層中可能會導致令人驚訝的巢狀事務案例,並且從可讀性的角度來看,可能很難理解其意圖。看一下以下程式:
with newTransaction(): Accounts.create("609-543-222") with newTransaction(): Accounts.create("775-988-322") throw Rollback(); |
上面程式碼的結果是什麼?是要回滾這兩個事務還是僅回滾內部事務?如果我們依賴封裝了我們建立的事務的多層庫,將會發生什麼。我們是否能夠識別和改善此類情況?
想象一個具有多個操作(例如newAccount)的資料層已經在它們自己的事務中實現了。當您在自己的事務中執行的高階業務邏輯中執行它們時,會發生什麼?隔離和一致性特徵將是什麼?
function newAccount(id string) { with newTransaction(): Accounts.create(id) } |
不要處理此類開放式問題,而應避免巢狀事務。您的資料層仍然可以執行高階操作,而無需建立自己的事務。然後,業務邏輯可以啟動事務,在事務上執行操作,提交或中止。
function newAccount(id string) { Accounts.create(id) } // In main application: with newTransaction(): // Read some data from database for configuration. // Generate an ID from the ID service. Accounts.create(id) Uploads.create(id) // create upload queue for the user. |
14.事務不應維持應用程式狀態
應用程式開發人員可能希望在事務中使用應用程式狀態來更新某些值或調整查詢引數。要考慮的一件關鍵事情是考慮作用域/範圍界限(scope)。
發生網路問題時,客戶通常會重試交易。如果事務依賴於在其他地方發生了變異的狀態,則它可能會選擇錯誤的值,具體取決於問題中資料爭用的可能性。事務處理應謹慎對待應用程式內資料競爭。
var seq int64 with newTransaction(): newSeq := atomic.Increment(&seq) Entries.query(newSeq) // Other operations... |
上面的事務每次執行時都會增加序列號,無論最終結果如何。如果提交由於網路而失敗,則在第二次重試時,它將使用不同的序列號進行查詢。
15. Query planner可以告訴你資料庫情況
查詢計劃者確定如何在資料庫中執行查詢。他們還分析查詢並在執行之前對其進行優化。計劃者只能根據其擁有的訊號提供一些可能的估計。那麼如何找到以下查詢的結果:
SELECT * FROM articles where author = "rakyll" order by title; |
有兩種方法來檢索結果:
- 全表掃描:我們可以瀏覽表上的每個條目,並返回與作者姓名匹配的文章,然後進行排序。
- 索引掃描:我們可以使用索引來查詢匹配的ID,檢索那些行然後進行排序。
查詢計劃者的作用是確定哪種策略是最佳選擇。
查詢計劃人員只能預測有限的訊號,並可能導致錯誤的決策。DBA或開發人員可以使用它們來診斷和調整效果不佳的查詢。新版本的資料庫可以調整查詢計劃程式,如果新版本引入效能問題,則在資料庫升級時進行自我診斷可以為您提供幫助。諸如慢查詢日誌,延遲問題或執行時間統計之類的報告對於確定要優化的查詢很有用。
查詢計劃程式提供的某些指標可能很嘈雜,尤其是在估算延遲或CPU時間時。作為查詢計劃者的補充,即使不是每個資料庫都提供這樣的工具,跟蹤和執行路徑工具對於診斷這些問題可能更有用。
16. 線上遷移很複雜,但可能。
線上,實時或實時遷移意味著從一個資料庫遷移到另一個資料庫而不會造成停機,並且不會影響資料的正確性。如果要遷移到相同的資料庫/引擎,則實時遷移會更容易,但是當遷移到具有不同效能特徵和架構要求的新資料庫時,實時遷移會變得更加複雜。
線上遷移有多種模式,以下是其中一種:
- 開始對兩個資料庫進行雙重寫入。在此階段,新資料庫將不會擁有所有資料,但會開始檢視新資料。一旦對這一步充滿信心,就可以繼續進行第二步。
- 開始啟用讀取路徑以同時使用兩個資料庫。
- 新資料庫主要用於讀取和寫入。
- 儘管繼續從舊資料庫讀取資料,但請不要繼續寫入舊資料庫。此時,新資料庫仍不具有所有新資料,您可能需要回退到舊資料庫以獲取舊記錄。
- 此時,舊資料庫是隻讀的。用舊資料庫中缺少的資料回填新資料庫。遷移完成後,所有讀寫路徑都可以使用新資料庫,並且舊資料庫可以從系統中刪除。
如果需要更多種姓研究,請參閱Stripe 關於該模型遵循的遷移策略的綜合文章。
17.資料庫的顯著增長帶來了不可預測性
隨著資料增長,以前對資料大小和網路容量要求的假設或期望可能會過時。這是大型方案重寫,大規模操作改進,容量問題,部署重新考慮或遷移到其他資料庫以避免中斷的時候。
相關文章
- 好程式設計師分享ApacheSpark常見的三大誤解程式設計師ApacheSpark
- Python程式設計師的10個常見錯誤Python程式設計師
- 好程式設計師分享JavaScript中8個常見的陷阱程式設計師JavaScript
- 程式設計師可能犯的3個常見SQL錯誤程式設計師SQL
- 好程式設計師分享JavaScript幾個最常見的錯誤程式設計師JavaScript
- Python程式設計師的常見錯誤Python程式設計師
- Java程式設計師可能犯的3個常見SQL錯誤Java程式設計師SQL
- 好程式設計師web前端分享常見面試題程式設計師Web前端面試題
- 常見的資料分析誤區
- Web程式設計師常見的5個錯誤及解決方案Web程式設計師
- 好程式設計師大資料培訓分享之hive常見自定義函式程式設計師大資料Hive函式
- 程式設計師最常見的技術性誤區程式設計師
- 好程式設計師Java教程分享XML常見面試題程式設計師JavaXML面試題
- 好程式設計師分享:Java面試題常見問題程式設計師Java面試題
- Mark Lutz:Python程式設計師的常見錯誤Python程式設計師
- 程式設計面試中的十個常見錯誤程式設計面試
- 常見資料分析誤區有哪些?
- 10個常見的快取使用誤區快取
- 好程式設計師大資料培訓分享常見的Hadoop和Spark專案程式設計師大資料HadoopSpark
- Python 常見的17個錯誤分析Python
- 好程式設計師Java教程分享:Java工程師常見面試題程式設計師Java工程師面試題
- 好程式設計師web前端教程分享Jquery常見面試題程式設計師Web前端jQuery面試題
- 好程式設計師Java教程分享JavaScript常見面試題五程式設計師JavaScript面試題
- 好程式設計師Java教程分享JavaScript常見面試題四程式設計師JavaScript面試題
- 好程式設計師Java教程分享JavaScript常見面試題三程式設計師JavaScript面試題
- 好程式設計師Java教程分享JavaScript常見面試題二程式設計師JavaScript面試題
- 好程式設計師Java教程分享JavaScript常見面試題一程式設計師JavaScript面試題
- 程式設計師世界常見的6個問題程式設計師
- 談談資料安全常見的誤區
- 資料治理常見的誤區有哪些
- windows程式設計常見資料型別Windows程式設計資料型別
- 好程式設計師分享javascript中的常見的相容寫法程式設計師JavaScript
- 好程式設計師web前端分享用JavaScript實現的5個常見函式程式設計師Web前端JavaScript函式
- 盤點PHP程式設計常見失誤PHP程式設計
- 資料庫人員:常見錯誤(轉)資料庫
- 美女程式設計師觀點:程式設計師最重要的非程式設計技巧程式設計師
- 好程式設計師Java培訓分享Java效能常見命令有哪些程式設計師Java
- 好程式設計師Python教程分享Python常見面試問題程式設計師Python面試