美女程序媛:使用數據庫時,這17個誤區,男同胞總沒法避免!

轉載自:https://www.jdon.com/54013

美女程序媛:使用數據庫時,這17個誤區,男同胞總沒法避免!

絕大多數計算機系統都具有某種狀態,並且可能依賴於存儲系統。我對數據庫的瞭解是隨著時間的推移而積累的,但是在此過程中,我們的設計錯誤導致了數據丟失和中斷。在數據繁重的系統中,數據庫是系統設計目標和折衷方案的核心。

在本系列中,我將分享一些我特別發現的見解,這些見解對那些不擅長該領域的開發人員非常有用。

  1. 如果99.999%的時間網絡都不成問題,您將很幸運。
  2. ACID有很多含義。
  3. 每個數據庫具有不同的一致性和隔離功能。
  4. 當您無法持有鎖時,可以使用樂觀鎖。
  5. 除了髒讀和數據丟失外,還有其他異常。
  6. 我和我的數據庫並不總是就順序處理達成一致。
  7. 應用程序級分片可以存在於應用程序外部。
  8. 主鍵自動增加AUTOINCREMENT可能有害。
  9. 過時的數據可能有用且無鎖。
  10. 任何時鐘源之間都會發生時鐘偏斜。
  11. 延遲有很多含義。
  12. 評估每筆交易的性能要求。
  13. 嵌套交易可能有害。
  14. 事務不應維持應用程序狀態。
  15. 查詢計劃人員可以提供很多有關數據庫的知識。
  16. 在線遷移很複雜,但可能。
  17. 數據庫的顯著增長帶來了不可預測性。

1. 如果99.999%的時間網絡都不成問題,您將很幸運。

如果網絡問題僅佔導致中斷的潛在問題的一小部分,您就很幸運。網絡仍然遭受常規問題的困擾,例如硬件故障,拓撲更改,管理配置更改和電源故障。

2. ACID有很多含義

ACID代表原子性,一致性,隔離性,耐用性。這些是數據庫事務需要確保其用戶有效性的屬性,即使發生崩潰,錯誤,硬件故障或類似情況也是如此。如果沒有ACID或類似的合同,應用程序開發人員將無法確定他們的職責以及數據庫提供的內容。大多數關係事務數據庫都試圖符合ACID,但是諸如NoSQL運動之類的新方法催生了許多沒有ACID事務的數據庫,因為它們實現起來很昂貴。

當我剛接觸該行業時,我們的技術主管在爭論ACID是否已過時。可以說ACID被認為是寬鬆的描述,而不是嚴格的實施標準。今天,我發現它最有用,因為它提供了一類問題(以及一類可能的解決方案)。

並非每個數據庫都符合ACID,並且在符合ACID的數據庫中,ACID的解釋可能有所不同。實施ACID的方式不同的原因之一是實施ACID功能涉及的權衡數量。數據庫可能將自己宣傳為ACID,但在邊緣情況或它們如何處理“不太可能”的事件時可能仍具有不同的解釋。開發人員至少可以從高層次上學習數據庫如何實現事物,以便對故障模式和設計權衡有一個正確的瞭解。

MongoDB已經擁有日誌功能,但是髒寫入仍然會影響數據的持久性,因為默認情況下它們每100毫秒提交一次日記。即使風險顯著降低,對於日誌的持久性以及這些日誌中表示的更改,仍然可能存在相同的情況。

3. 每個數據庫具有不同的一致性和隔離功能。

在ACID屬性中,一致性和隔離性具有最大的不同實現細節範圍,因為折衷範圍更廣。一致性和隔離性是昂貴的功能。他們需要協調並正在增加爭用以保持數據的一致性。當必須在數據中心之間(尤其是在不同地理區域之間)進行水平擴展時,問題將變得更加困難。隨著可用性的降低和網絡分區的發生越來越頻繁,提供高水平的一致性可能會非常困難。

CAP定理對此現象進行更一般的解釋。還值得一提的是,應用程序可以處理一些不一致的情況,否則程序員可能對問題有足夠的瞭解,可以在應用程序中添加其他邏輯來處理該問題,而無需過多依賴其數據庫。

數據庫通常提供各種隔離層,因此應用程序開發人員可以根據自身的權衡取捨最具成本效益的層。較弱的隔離可能會更快,但可能會導致數據爭用。更強的隔離消除了一些潛在的數據爭用,但是會更慢,並且可能引入爭用,從而使數據庫減慢到可能導致中斷的程度。

美女程序媛:使用數據庫時,這17個誤區,男同胞總沒法避免!

上圖是現有併發模型及其之間的關係的概述。

即使在理論上和實踐上還有更多的隔離級別,SQL標準也只定義了四個隔離級別。如果需要進一步閱讀,jepson.io提供了有關現有併發模型的引人注目的概述。例如,Google的Spanner通過時鐘同步來保證外部可串行化,即使這是一個嚴格的隔離層,也沒有在標準隔離層中定義。

SQL標準中提到的隔離級別為:

可序列化(最嚴格,最昂貴):可序列化的執行與那些事務的某些串行執行產生相同的效果。串行執行是指每個事務在下一個事務開始之前執行完成的過程。關於可序列化級別的一個注意事項是,由於解釋上的差異,通常將其實現為“快照隔離”(例如Oracle),並且SQL標準中未表示“快照隔離”。

可重複讀取:當前事務中未提交的讀取對於當前事務是可見的,但其他事務(例如新插入的行)所做的更改將不可見。

已提交讀:未提交的讀對事務不可見。只有已提交的寫入是可見的,但是幻像讀取可能會發生。如果另一個事務插入並提交新行,則當前事務在查詢時可以看到它們。

未提交讀(最不嚴格,便宜):允許進行髒讀,事務可以看到其他事務尚未提交的更改。實際上,此級別對於返回近似聚合(例如對錶的COUNT(*)查詢)很有用。

可序列化級別雖然成本最高且引入了最多的系統爭用,但卻使發生數據爭用的機會最少。其他隔離級別更便宜,但增加了數據爭用的可能性。有些數據庫允許您設置隔離級別,有些數據庫則對它們有更多的看法,而不一定支持所有數據庫。

即使數據庫宣傳其對這些隔離級別的支持,但仔細檢查它們的行為仍可能提供有關實際操作的更多見解。

Kleppmann的hermitage提供不同的併發異常的概述和數據庫是否能夠在特定的隔離級別來處理它。他的研究表明,數據庫設計師可以如何不同地解釋隔離級別。

4. 當您無法持有鎖時,可以使用樂觀鎖。

鎖不僅成本高昂,不僅因為它們會在數據庫中引入更多爭用,而且可能需要從應用程序服務器到數據庫的一致連接。排他鎖可能會更嚴重地受到網絡分區的影響,並導致難以識別和解決的死鎖。如果無法持有排他鎖,則可以選擇樂觀鎖。

樂觀鎖定是一種讀取行,記錄版本號,最近修改的時間戳或其校驗和的一種方法。然後,您可以在更改記錄之前檢查版本是否沒有原子更改。

<code>UPDATE products
SET name = 'Telegraph receiver', version = 2
WHERE id = 1 AND version = 1、/<code>

如果另一個更新之前更改了該行,則對產品表的更新將影響0行。如果沒有更早的更新,它將影響1行,並且我們可以判斷我們的更新已成功。

5.除了髒讀和數據丟失外,還有其他異常。

當我們談論數據一致性時,我們主要關注可能導致競爭性讀取和數據丟失的競爭狀況。但是數據異常不僅限於此。

這種類型的異常的一個例子是寫偏斜。寫偏斜很難識別,因為我們沒有積極尋找它們。當寫操作發生髒讀或丟失時,不會造成寫偏斜,但會損害數據的邏輯約束。

例如,假設一個監視應用程序要求多個個操作者中的一個始終處於oncall狀態為真。

<code>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;/<code>

在上述情況下,如果兩個事務成功提交,將存在寫偏斜。即使沒有發生髒讀或數據丟失的情況,數據的完整性也會丟失,因為指定了兩個oncall狀態設置為True。

可序列化的隔離,模式設計或數據庫約束可以幫助消除寫偏斜。開發人員必須能夠在開發過程中識別此類異常,以避免生產中出現數據異常。話雖如此,要識別代碼庫中的寫偏斜可能非常困難。尤其是在大型系統中,如果不同的團隊負責基於相同的表構建要素,而又彼此不溝通並且不檢查他們如何訪問數據。

可序列化的隔離,模式設計或數據庫約束可以幫助消除寫偏斜。開發人員必須能夠在開發過程中識別此類異常,以避免生產中出現數據異常。話雖如此,要識別代碼庫中的寫偏斜可能非常困難。尤其是在大型系統中,如果不同的團隊負責基於相同的表構建要素,而又彼此不溝通並且不檢查他們如何訪問數據。

6.我和我的數據庫並不總是就順序處理達成一致。

數據庫提供的核心功能之一是順序保證,但是順序對於應用程序開發人員可能是令人驚訝的。數據庫按照事務接收的順序查看事務,而不是按開發人員看到的編程順序查看事務。事務執行的順序很難預測,尤其是在大量併發系統中。

在開發期間,尤其是在使用非阻塞庫時,較差的樣式和可讀性可能會導致以下問題:用戶認為事務可以順序執行,即使它們可以以任何順序到達數據庫。下面的程序使T1和T2看起來將被順序調用,但是如果這些函數是非阻塞的並且立即以承諾返回,則調用的順序將取決於它們在數據庫中收到的時間。

<code>result1 = T1()//結果實際上是承諾promise 
result2 = T2()/<code>

如果需要原子性(完全提交或中止所有操作)並且順序很重要,則T1和T2中的操作應在單個數據庫事務中運行。

美女程序媛:使用數據庫時,這17個誤區,男同胞總沒法避免!

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時,類似的經驗法則也在考慮這種故障。

在收集每個操作的指標時,請注意高基數。如果需要高基數調試數據,請使用日誌,甚至收集或分佈式跟蹤。請參閱要調試延遲嗎?有關延遲調試方法的概述

美女程序媛:使用數據庫時,這17個誤區,男同胞總沒法避免!

13.嵌套事務可能有害

並非每個數據庫都支持嵌套事務,但是當嵌套數據庫支持嵌套事務時,嵌套事務可能會導致令人驚訝的編程錯誤,這些錯誤通常很難被發現,除非您清楚看到異常。

如果您想避免嵌套事務,則客戶端庫可以進行工作來檢測和避免嵌套事務。如果無法避免,則必須注意避免出現意外情況,在這種情況下,已提交的事務由於子事務而意外中止。

將事務封裝在不同的層中可能會導致令人驚訝的嵌套事務案例,並且從可讀性的角度來看,可能很難理解其意圖。看一下以下程序:

<code>with newTransaction():
Accounts.create("609-543-222")
with newTransaction():
Accounts.create("775-988-322")
throw Rollback();/<code>

上面代碼的結果是什麼?是要回滾這兩個事務還是僅回滾內部事務?如果我們依賴封裝了我們創建的事務的多層庫,將會發生什麼。我們是否能夠識別和改善此類情況?

想象一個具有多個操作(例如newAccount)的數據層已經在它們自己的事務中實現了。當您在自己的事務中運行的高級業務邏輯中運行它們時,會發生什麼?隔離和一致性特徵將是什麼?

<code>function newAccount(id string) {
with newTransaction():
Accounts.create(id)
}/<code>

不要處理此類開放式問題,而應避免嵌套事務。您的數據層仍然可以執行高級操作,而無需創建自己的事務。然後,業務邏輯可以啟動事務,在事務上運行操作,提交或中止。

<code>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./<code>

14.事務不應維持應用程序狀態

應用程序開發人員可能希望在事務中使用應用程序狀態來更新某些值或調整查詢參數。要考慮的一件關鍵事情是考慮作用域/範圍界限(scope)。

發生網絡問題時,客戶通常會重試交易。如果事務依賴於在其他地方發生了變異的狀態,則它可能會選擇錯誤的值,具體取決於問題中數據爭用的可能性。事務處理應謹慎對待應用程序內數據競爭。

<code>var seq int64 

with newTransaction():
newSeq := atomic.Increment(&seq)
Entries.query(newSeq)
// Other operations.../<code>

上面的事務每次運行時都會增加序列號,無論最終結果如何。如果提交由於網絡而失敗,則在第二次重試時,它將使用不同的序列號進行查詢。

15. Query planner可以告訴你數據庫情況

查詢計劃者確定如何在數據庫中執行查詢。他們還分析查詢並在運行之前對其進行優化。計劃者只能根據其擁有的信號提供一些可能的估計。那麼如何找到以下查詢的結果:

<code>SELECT * FROM articles where author = "rakyll" order by title;/<code>

有兩種方法來檢索結果:

  • 全表掃描:我們可以瀏覽表上的每個條目,並返回與作者姓名匹配的文章,然後進行排序。
  • 索引掃描:我們可以使用索引來查找匹配的ID,檢索那些行然後進行排序。

查詢計劃者的作用是確定哪種策略是最佳選擇。

查詢計劃人員只能預測有限的信號,並可能導致錯誤的決策。DBA或開發人員可以使用它們來診斷和調整效果不佳的查詢。新版本的數據庫可以調整查詢計劃程序,如果新版本引入性能問題,則在數據庫升級時進行自我診斷可以為您提供幫助。諸如慢查詢日誌,延遲問題或執行時間統計之類的報告對於確定要優化的查詢很有用。

查詢計劃程序提供的某些指標可能很嘈雜,尤其是在估算延遲或CPU時間時。作為查詢計劃者的補充,即使不是每個數據庫都提供這樣的工具,跟蹤和執行路徑工具對於診斷這些問題可能更有用。

16. 在線遷移很複雜,但可能。

在線,實時或實時遷移意味著從一個數據庫遷移到另一個數據庫而不會造成停機,並且不會影響數據的正確性。如果要遷移到相同的數據庫/引擎,則實時遷移會更容易,但是當遷移到具有不同性能特徵和架構要求的新數據庫時,實時遷移會變得更加複雜。

在線遷移有多種模式,以下是其中一種:

  • 開始對兩個數據庫進行雙重寫入。在此階段,新數據庫將不會擁有所有數據,但會開始查看新數據。一旦對這一步充滿信心,就可以繼續進行第二步。
  • 開始啟用讀取路徑以同時使用兩個數據庫。
  • 新數據庫主要用於讀取和寫入。
  • 儘管繼續從舊數據庫讀取數據,但請不要繼續寫入舊數據庫。此時,新數據庫仍不具有所有新數據,您可能需要回退到舊數據庫以獲取舊記錄。
  • 此時,舊數據庫是隻讀的。用舊數據庫中缺少的數據回填新數據庫。遷移完成後,所有讀寫路徑都可以使用新數據庫,並且舊數據庫可以從系統中刪除。

如果需要更多種姓研究,請參閱Stripe 關於該模型遵循的遷移策略的綜合文章。

17.數據庫的顯著增長帶來了不可預測性

隨著數據增長,以前對數據大小和網絡容量要求的假設或期望可能會過時。這是大型方案重寫,大規模操作改進,容量問題,部署重新考慮或遷移到其他數據庫以避免中斷的時候。

美女程序媛:使用數據庫時,這17個誤區,男同胞總沒法避免!


分享到:


相關文章: