《Uber | 從PostgreSQL到MySQL》要點:
本文介紹了Uber | 從PostgreSQL到MySQL,希望對您有用。如果有疑問,可以聯系我們。
相關主題:PostgreSQL教程
導讀:近期 Uber 宣布將數據庫從 Postgres 遷移到 MySQL,在多個技術社區中引起了軒然大波,通過本文我們來詳細了解 Uber 做出以上決策背后的原因.
介紹
Uber 的早期架構是由 Python 編寫一個單體后端應用程序,使用 Postgres 作為數據持久化.后來 Uber 架構經歷一系列顯著改變,朝著微服務架構和新的數據平臺發展.具體而言,在許多以前使用的 Postgres 的場景,現在更多的使用構建在 MySQL 之上的 schemaless 存儲系統(小編:Uber的數據中間件).在本文中,將探討一些我們發現的 Postgres 的弊端,并解釋我們切換 schemaless 和其他后端服務到 MySQL 數據庫的原因.
Postgres 架構概述
我們遇到的大量 Postgres 限制如下:
效率低下的寫入架構
低效的數據復制
表損壞的問題
糟糕的 MVCC 從庫支持
難以升級到新的版本
我們將在所有這些限制,首先通過分析 Postgres 如何組織在磁盤上的表和索引進行分析,特別是比較與 MySQL 使用 InnoDB 存儲相同數據的實現方式.需要注意的是,我們在這里提出的分析 主要是基于我們有些老的 Postgres 9.2 版本系列的經驗.但據我們所知,本文中討論的 PG 內部架構,并沒有顯著在新的 Postgres 版本中改變,就如在 9.2 版的磁盤數據設計,也沒有比 Postgres 的 8.3 版(10 年前的版本)有什么顯著變化.
磁盤數據格式
關系數據庫必須執行一些關鍵任務:
提供插入/更新/刪除功能
進行 schema 更改的能力
實現多版本并發控制 (MVCC)機制,使不同的連接分別有他們各自的數據的事務視圖
考慮如何將上述這些功能組合在一起工作,是數據庫設計時的重要考慮部分.
Postgres 的核心設計之一是不變的(immutable)行數據.這些不變的行在 Postgres 中稱為“tuple”.Tuple 在 Postgres 內部實現中由 CTID 來唯一標識 .一個 CTID 代表 tuple 在磁盤上的位置(即物理磁盤偏移).多個 ctid 可以潛在描述一個單列(例如,當用于 MVCC 目的,或存在的行的多個版本時,行的舊版本尚未被 autovacuum 回收處理).有組織的 tuple 的集合形成表.表本身具有的索引,通常被組織為 B 樹數據結構,映射索引字段到 CTID 的負載.
通常,這些 ctids 對用戶透明,但知道他們是如何工作,可以幫助您了解 Postgres 在磁盤上的數據結構.要查看某行當前 CTID,可以在查詢的時候顯式加上 “CTID”:
為了解釋布局的細節,讓我們考慮一個簡單的用戶表的例子.對于每個用戶,我們有一個自動遞增的用戶 ID 的主鍵,還有用戶的名字和姓氏,以及用戶的出生年份.我們還定義了用戶的全名復合二級索引(姓和名),并在用戶的出生年份加上另一個二級索引.創建這樣一個表 DDL 可能是這樣的:
注意這個定義中的三個索引:主鍵索引加上兩個二級索引.
對于本文中的例子,我們看下表的數據,它由一個選擇有影響力的歷史數學家開始:
如上所述,每行中隱含有獨特的,不透明 CTID.因此,我們可以這樣理解表的內部結構:
主鍵索引,它映射 ID 與 ctids,是這樣定義的:
B 樹被用在 id 字段上,B 樹中的每個節點上保存了 CTID 值.注意,在這種情況下,在 B 樹的字段的順序,剛好與表中順序相同,這是由于使用自動遞增的 id 的緣故,但這并不一定需要是這種情況.
二級索引看起來相似;主要的區別是字段存儲順序不同,因為 B 樹,必須按字典順序組織.姓名索引(first,last)按字母表的順序排列:
同樣,birth_year 聚簇索引按升序排列,就像這樣:
正如你所看到的,在這兩種情況下,在各自的二級索引 CTID 字段本身并不是有序的,不象第一個自動遞增的主鍵的情況.
假設我們需要更新此表中的記錄.舉例來說,假設要更新 al-Khwārizmī’ 的出生年份到 770 CE.正如前面提到的,行的 tuple 是不可變的.因此,要更新記錄,需要添加一個新的 tuple.這種新的 tuple 有一個新的不透明 CTID,我們稱之為 I.Postgres 需要能夠從舊的 tuple D 處找到新的 I.在內部,Postgres 存儲每個 tuple 中的版本字段,以及指向前一 tuple 的 ctid 指針(如果有).因此,該表的新結構如下:
只要 al-Khwārizmī 的兩個版本存在,索引則必須維護兩行的記錄.為簡單起見,我們省略了主鍵索引并顯示只有在這里的二級索引,它是這樣的:
我們將舊版本標識成紅色,將新版標識成綠色.在此之下,Postgres 使用另一個字段來保存該行版本,以確定哪一個 tuple 是最新的.這個新增的字段允許數據庫確定事務看到的是那一個行的 tuple.
在 Postgres,主索引和二級索引都指向磁盤上的 tuple 偏移.當一個 tuple 的位置變化,各項索引都必須更新.
復制
當我們插入數據到表中,如果啟用了流復制機制,Postgres 將會對數據進行復制,處于崩潰恢復的目的,數據庫啟用了預寫日志 (WAL)并使用它來實現兩階段提交(2PC).即使不啟用復制的情況下,數據庫也必須保留 WAL ,因為 WAL 提供了 ACID 的原子性(Atomicity)及持久性(Durability)能力.
我們可以通過如下場景來更好的理解 WAL,如果數據庫遇到突然斷電時意外崩潰,WAL 就提供了磁盤上表與索引更新變化的一個賬本.當 Postgres 的守護程序再次啟動后,就會對比賬本上的記錄與磁盤上的實際數據是否一致.如果帳本包含未在磁盤上的體現的數據,則可以利用 WAL 的記錄來修正磁盤上的數據.
另外一方面,Postgres 也利用 WAL 將其在主從之間發送來實現流復制功能.每個從庫復制數據與上述崩潰恢復的過程類似.流復制與實際崩潰恢復之間的唯一區別是,在恢復數據過程中是否能對外提供數據訪問服務.
由于 WAL 實際上是為崩潰恢復目的而設計,它包含在物理磁盤的低級別更新的信息.WAL 記錄的內容是在行 tuple 和它們的磁盤偏移量(即一行 ctids) 的實際磁盤上的代表級別.如果暫停一個 Postgres 主庫,從庫數據完全趕上后,在從庫的實際磁盤上的內容完全匹配主庫.因此,像工具 rsync 都可以恢復一個同步失敗的從庫.
Postgres 上述設計的大坑
Postgres 的上述設計給 Uber 在 PG 的使用上,導致了效率低下和其他很多問題.
1. 寫放大(Write Amplification)
在 Postgres 設計的第一個問題是已知的寫入放大 .
通常的寫入放大是指一種問題數據寫入,比如在 SSD 盤上,一個小邏輯更新(例如,寫幾個字節)轉換到物理層后,成為一個更大的更昂貴的更新.
同樣的問題也出現在 Postgres,在上面的例子,當我們做出的小邏輯更新,比如修改 al-Khwārizmī 的出生年份時,我們不得不執行至少四個物理的更新:
在表空間中寫入新行的 tuple;
為新的 tuple 更新主鍵索引;
為新的 tuple 更新姓名索引 (first, last) ;
更新 birth_year 索引,為新的 tuple 添加一條記錄;
事實上,這四步更新僅為了反映一個到主表的寫操作;并且每個這些寫入也同樣需要在 WAL 得到體現,所以在磁盤上寫入的總數目甚至比 4 步更大.
值得一提的是這里更新 2 和 3. 當我們更新了 al-Khwārizmī 的出生年份,我們實際上并沒有改變他的主鍵,我們也沒有改變他的名字和姓氏.然而,這些索引仍必須與創建在數據庫中的行記錄了新的行的 tuple 的更新. 對于具有大量二級索引的表,這些多余的步驟可能會導致巨大的低效.舉例來說,如果我們有一個表上定義了十幾個二級索引,更新一個字段,僅由一個單一的索引覆蓋必須傳播到所有的 12 項索引,以反映新行的 CTID.
2. 復制
因為復制發生在磁盤的變化上,因此寫入放大問題自然會轉化為復制層的放大.一個小的邏輯記錄,如“更改出生年份為 CTID D 到 770”,WAL 會將上述描寫的 4 步從網絡上同步到從庫,因此寫入放大問題也等同一個復制放大問題,從而 Postgres 的復制數據流很快變得非常冗長,可能會占用大量的帶寬.
在 Postgres 的復制發生一個數據中心內的情況下,復制帶寬可能不是一個問題.現代網絡設備和交換機可以處理大量的帶寬,許多托管服務提供商提供免費或廉價的內部數據中心帶寬.然而, 當復制必須在不同數據中心之間發生的,問題都可以迅速升級 .
例如,Uber 原本使用的物理服務器在西海岸機房.為了災難恢復的目的,我們在東海岸托管空間添加了一批服務器.在本設計中,我們西部數據中心作為主庫,東海岸增加了一批服務器作為從庫.
級聯復制可以降低跨數據中心的帶寬要求,只需要主庫和一個從庫之間同步一份數據所需的帶寬和流量,即便在第二個數據中心配置了多個從庫.然而,Postgres 的復制協議的詳細程度,對于使用了大量二級索引的數據庫,仍可能會導致數據的海量傳輸.采購跨國的帶寬是昂貴的,即使有錢的土豪公司,也無法做到跨國的帶寬和本地的帶寬一樣大.
這種帶寬的問題也導致我們曾經在 WAL 歸檔方面出現過問題.除了發送所有從西海岸到東海岸的 WAL 更新,我們將所有的 WAL 記錄歸檔到一個文件存儲的 Web 云服務,這樣當出現數據災難情況時,可以從備份的 WAL 文件恢復.但是流量峰值時段,我們與存儲網絡服務的帶寬根本無法跟上 WAL 寫入的速度.
3. 數據損壞
在一次例行主數據庫擴容的變更中,我們遇到了一個 Postgres 9.2 的 bug.從庫的切換時間順序執行不當,導致他們中的一些節點誤傳了一些 WAL 記錄.因為這個 bug,應該被標記為無效的部分記錄未標記成無效.
以下查詢說明了這個 bug 如何影響我們的用戶表:
SELECT * FROM users WHERE ID = 4;
此查詢將返回兩條記錄:修改出生年份之前的老記錄,再加上修改后的新記錄.如果將 CTID 添加到 WHERE 列表中,我們將看到返回記錄中存在不同的 CTID 記錄,正如大家所預料的,返回了兩個不同行的 tuple.
這個問題是有幾個原因非常傷腦筋.首先,我們不能輕易找出這個問題影響的行數.從數據庫返回的結果重復,導致應用程序邏輯在很多情況下會失敗.我們最終使用防守編程語句來檢測已知有這個問題表的情況.因為 bug 影響所有服務器,損壞的行在不同的服務器節點上可能是不同的,也就是說,在一個從庫行 X 可能是壞的,Y 是好的,但對另一個從庫,用行 X 可能是好的,Y 行可能是壞. 事實上,我們并不確定數據損壞的從庫節點數量,以及主庫是否也存在數據損壞.
雖然我們知道,問題只是出現在每個數據庫的少量幾行,但我們還是非常擔心, 因為 Postgres 復制機制發生在物理層 ,任何小的錯誤格式有可能會導致徹底損壞我們的數據庫索引.B 樹的一個重要方面是,它們必須定期重新平衡 ,并且這些重新平衡操作可以完全改變樹的結構作為子樹被移到新的磁盤上的位置.如果錯誤數據被移動,這可能會導致樹的大部分地區變得完全無效.
最后,我們追蹤到了實際的 bug,并用它來確定新的 master 不存在任何損壞行.然后再把 master 的快照同步到所有從庫上去, 這是一個艱苦的體力活的過程 (小編:看到美帝的 DBA 也這么苦逼心理終于平衡一點了),因為我們每次只能從在線的池子里面拿出有限幾臺來操作.
雖然我們遇到的這個 bug 僅影響 Postgres 9.2 的某些版本,而且目前已經修復了很久.但是,我們仍然發現這類令人擔憂的 bug 可以再次發生.可能任意一個新的 Postgres 版本,它會帶著這種致命類型的 bug,而且由于其復制的不合理的設計,這個問題一旦出現,就會立即蔓延到集群中所有復制鏈的數據庫上.
4. 從庫無 MVCC
Postgres 沒有真正的從庫 MVCC 支持.在從庫任何時刻應用 WAL 更新,都會導致他們與主庫物理結構完全一致.這樣的設計也給 Uber 帶來了一個問題.
為了支持 MVCC,Postgres 需要保留行的舊版本.如果流復制的從庫正在執行一個事務,所有的更新操作將會在事務期間被阻塞.在這種情況下,Postgres 將會暫停 WAL 的線程,直到該事務結束.但如果該事務需要消耗相當長的時間,將會產生潛在的問題,Postgres 在這種情況下設定了超時:如果一個事務阻塞了 WAL 進程一段時間,Postgres 將會 kill 這個事務.
這樣的設計意味著從庫會定期的滯后于主庫,而且也很容易寫出代碼,導致事務被 kill.這個問題可能不會很明顯被發現.例如,假設一個開發人員有一個收據通過電子郵件發送給用戶一些代碼.這取決于它是如何寫的,代碼可能隱含有一個的保持打開,直到郵件發送完畢后,再關閉的一個數據庫事務.雖然它總是不好的形式,讓你的代碼舉行公開的數據庫事務,同時執行無關的阻塞 I / O,但現實情況是,大多數工程師都不是數據庫專家,可能并不總是理解這個問題,特別是使用掩蓋了低級別的細節的 ORM 的事務.(小編:美帝程序員代碼習慣跟咱們也很類似)
Postgres 的升級
因為復制記錄在物理層面工作,這導致不能在不同的 Postgres GA 版本之間進行復制.運行的 Postgres 9.3 主數據庫無法復制數據到 Postgres 9.2 的從庫上,也無法在運行 9.2 的主數據庫復制數據到 Postgres 9.3 的從庫上.
我們按照以下這些步驟,從一個 Postgres 的 GA 版本升級到另一個:
關閉主數據庫.
在主庫上運行 pg_upgrade 命令,這是更新主庫數據的命令 .在一個大的數據庫上,這很容易需要幾個小時的時間,執行期間不能夠提供任何訪問服務.
再次啟動主庫.
創建主庫的新快照,這一步完全復制一份主庫的所有數據,因此對于大型數據庫,它也需要幾個小時的時間.
清除所有從庫上的數據,將從主庫導出的快照恢復到所有從庫.
把每個從庫恢復到原先的復制層次結構.等待從庫追上主庫的最新的更新數據.
我們使用上述方法將 Postgres 9.1 成功升級到 Postgres 9.2.然而,這個過程花了太多時間,我們不能接受這個過程再來一次.到 Postgres 9.3 出來時,Uber 的增長導致我們的數據大幅增長,所以升級時間將會更加漫長. 出于這個原因,我們的 Postgres 的實例一直運行 Postgres 9.2 到今天,盡管當前的 Postgres GA 版本是 9.5 .
如果你正在運行 Postgres 9.4 或更高版本,你可以使用類似 pglogical,它實現了 Postgres 的一個邏輯復制層.使用 pglogical,可以在不同的 Postgres 版本之間復制數據,這意味著升級比如從 9.4 到 9.5,不會產生顯著的停機時間.但這個工具的能力依然存疑,因為它沒有集成到 Postgres 主干,另外對于老版本的用戶,pglogical 仍然不能支持.
MySQL 架構概述
為了更進一步解釋的 Postgres 的局限性,我們了解為什么 MySQL 是 Uber 新存儲工程 Schemaless 的底層存儲 .在許多情況下,我們發現 MySQL 更有利于我們的使用場景.為了了解這些差異,我們考察了 MySQL 的架構,并與 Postgres 進行對比.我們特別分析 MySQL 和 InnoDB 存儲引擎如何一同工作.Innodb 不僅在 Uber 大量使用,它也是世界上使用最廣泛的 MySQL 存儲引擎.
InnoDB 的磁盤數據結構
與 Postgres 一樣,InnoDB 支持如 MVCC 和可變數據這樣的高級特性.詳細討論 InnoDB 的磁盤數據格式超出了本文的范圍;在這里,我們將重點放在從 Postgres 的主要區別上.
最重要的架構區別在于 Postgres 的索引記錄直接映射到磁盤上的位置時,InnoDB 保持二級結構.而不是拿著一個指向磁盤上的行位置(如 CTID 在 Postgres),InnoDB 的第二個索引記錄持有一個指向主鍵值.因此,在 MySQL 中的二級索引與相關聯的主鍵索引鍵,是如下所示:
為了執行上的(first, last)索引查找,我們實際上需要做兩查找.第一次查找表,找到記錄的主鍵.一旦找到主鍵,則根據主鍵找到記錄在磁盤上的位置.
這種設計意味著 InnoDB 對 Postgres 在做非主鍵查找時有小小的劣勢,因為 MySQL 要做兩次索引查找,但是 Postgres 只用做一次.然后因為數據是標準化的,行更新的時候只需要更新相應的索引記錄.
而且 InnoDB 通常在相同的行更新數據,如果舊事務因為 MVCC 的 MySQL 從庫而需要引用一行,老數據將進入一個特殊的區域,稱為回滾段.
如果我們更新 al-Khwārizmī 的出生年份,我們看會發生什么.如果有足夠的空間,數據庫會直接更新 ID 為 4 的行(更新出生年份不需要額外的空間,因為年份是定長的 int).出生年份這一列上的索引同時也會被更新.這一行的老版本被復制到回滾段.主鍵索引不需要更新,同樣姓名索引也不需要更新.如果在這個表上有大量索引,數據庫需要更新包含了 birth_year 的索引.因此,我們并不需要更新 signup_date,last_login_time 這些索引,而 Postgres 則必須全更新一遍.
這樣的設計也使得 vocuum 和壓縮效率更高.所有需要 vocuum 的數據都在回滾段內.相比之下,Postgres 的自動清理過程中必須做全表掃描,以確定刪除的行.
MySQL 使用額外的間接層:二級索引記錄指向主索引記錄,而主索引本身包含在磁盤上的排的位置.如果一個行偏移的變化,只有主索引需要更新.
復制
MySQL 支持多個不同的復制模式:
語句級別的復制:復制 SQL語句(例如,它會從字面上直譯復制的語句,如:更新用戶 SET birth_year = 770 WHERE ID = 4 )
行級別的復制:復制所有變化的行記錄
混合復制:混合這兩種模式
這些模式都各有利弊.基于語句的復制通常最為緊湊,但可能需要從庫來支持昂貴的語句來更新少量數據.在另一方面,基于行的復制,如同 Postgres 的 WAL 復制,是更詳細,但會導致對從庫數據更可控,并且更新從庫數據更高效.
在 MySQL 中,只有主索引有一個指向行的磁盤上的指針.這個對于復制來說很重要.MySQL 的復制流只需要包含有關邏輯更新行的信息.復制更新如“更改行的時間戳 x 從 T_ 1 至 T_ 2 ”,從庫自動根據需要更新相關的索引.
相比之下,Postgres 的復制流包含物理變化,如“在磁盤偏移8382491,寫字節XYZ.” 在 Postgres 里,每一次磁盤物理改變都需要被記錄到 WAL 里.很小的邏輯變化(如更新時間戳)會引起許多磁盤上的改變:Postgres 必須插入新的 tuple,并更新所有索引指向新的 tuple.因此許多變化將被寫入 WAL.這種設計的差異意味著 MySQL 復制二進制日志是顯著比 PostgreSQL 的 WAL 流更緊湊.
復制如何工作也會影響從庫的 MVCC.由于 MySQL 的復制流使用邏輯的更新,從庫可以有真正的 MVCC 語義; 因此,讀庫查詢不會阻塞復制流.相比之下,Postgres 的 WAL 流包含物理磁盤上的變化,使得 Postgres 的從庫無法應用復制更新從而與查詢相沖突,所以 PG 復制不能實現 MVCC.
MySQL 的復制架構意味著,bug 也許會導致表損壞,但不太可能導致災難性的失敗.復制發生在邏輯層,所以像一個重新平衡 B tree 這樣的操作不會導致索引損壞.一個典型的 MySQL 復制問題是一個語句被跳過(或較少一點的情況,重復執行)的情況下.這可能會導致數據丟失或無效,但不會導致數據庫出現災難問題.
最后,MySQL 的復制架構使得它可以在 MySQL 不同版本之間進行復制.MySQL 只在復制格式改變的時候才增加版本號,這對 MySQL 來說很不常見.MySQL 的邏輯復制格式也意味著,在磁盤上的變化在存儲引擎層不影響復制格式.做一個 MySQL 升級的典型方法是在一個時間來更新應用到一個從庫,一旦你更新所有從庫,你可以把它提為新的 master.這個操作幾乎是 0 宕機的,這樣也能保證 MySQL 能及時得到更新.
其他 MySQL 設計優勢
到目前為止,我們集中于 Postgres 和 MySQL 在磁盤上的架構.MySQL 的架構導致性能比 Postgres 有顯著優勢.
緩沖池設計
首先,兩個數據庫緩沖池的工作方式不同.Postgres 用作緩存的內存比起內存的機器上的內存總數小很多.為了提高性能,Postgres 允許內核通過自動緩存最近訪問的磁盤數據的頁面緩存.舉例來說,我們最大的 Postgres 的節點有 768G 可用內存,但只有大約 25G 的內存實際上是被 Postgres 的 RSS 內存使用,這讓 700 多 GB 的可用內存留給 Linux 的頁面緩存.
這種設計的問題是,相比訪問 RSS 內存,操作系統的頁面緩存訪問數據實際上開銷更大.從磁盤查找數據,Postgres 執行 lseek 和 read 系統調用來定位數據.這些系統調用的招致上下文切換,這比從主存儲器訪問數據更昂貴.事實上,Postgres 在這方面完全沒有優化:Postgres 沒有利用的 pread(2)系統調用,pread 會合并 seed + read 操作成一個單一的系統調用.
相比之下,InnoDB 存儲引擎實現了自己的 LRUs 算法,它叫做 InnoDB 的緩沖池.這在邏輯上類似于 Linux 的頁面緩存,但在用戶空間實現的,因此也顯著比 Postgres 設計復雜,InnoDB 緩沖池的設計有一些巨大的優勢:
使得它可以實現一個自定義的 LRU 設計.例如,它可以檢測到病態的訪問模式,并且阻止這種模式給緩沖池帶來太大的破壞.
它導致更少的上下文切換.通過 InnoDB 緩沖池訪問的數據不需要任何用戶/內核上下文切換.最壞的情況下的行為是一個的出現 TLB miss,但是可以通過使用 huag page 來搞定.
連接處理
MySQL 的實現是對每個連接生成一個線程,相對來說開銷較低;每個線程擁有堆??臻g的一些內存開銷,再加上堆上分配用于連接特定的緩沖區一些內存.對 MySQL 來說擴展到 10,000 左右的并發連接不是罕見的事情,實事上我們現在的 MySQL 接近這個連接數.
Postgres 使用的是每連接一個進程的設計.這很明顯會比每連接每線程的設計開銷更大.啟動一個新的進程比一個新的線程會占用更多的內存.此外,線程之間進行通訊比進程之間 IPC 開銷低很多.Postgres 9.2 使用系統V IPC為IPC原語,而不是使用線程模型中輕量級的 futexes,futex 的非競爭是常見的情況,比 System V IPC 速度更快,不需要進行上下文切換.
除了與 Postgres 的設計相關聯的內存和 IPC 開銷,即使有足夠的可用內存可用, Postgres 對處理大連接數的支持依然非常差 .我們已經碰到擴展 Postgres 幾百個活動連接就碰到顯著的問題的情況,在官方文檔中也沒有確切的說明原因,它強烈建議使用獨立的連接池來保證大連接數.因此,使用 pgbouncer 做連接池基本可行.但是,在我們后端系統使用過程中發現有些 BUG,這會導致開啟大量的原本不需要的活躍連接,這些 BUG 也已經造成好幾次宕機.
結論
Postgres 在 Uber 初期運行的很好,但是 PG 很遺憾沒能很好適應我們的數據增長.今天,我們有一些遺留的 Postgres 實例,但我們的數據庫大部分已經遷移到 MySQL(通常使用我們的 Schemaless 中間層),在一些特殊的情況下,也使用 NoSQL 數據庫如 Cassandra.我們對 MySQL 的使用非常滿意,后續可能會在更多的博客文章中介紹其在 Uber 一些更先進的用途.
作者 Evan Klitzke 是 Uber 核心基礎架構組資深軟件工程師.他也是一個數據庫愛好者,是 2012 年 9 月加入 Uber 的一名早鳥.
英文原文:
https://eng.uber.com/mysql-migration/
維易PHP培訓學院每天發布《Uber | 從PostgreSQL到MySQL》等實戰技能,PHP、MYSQL、LINUX、APP、JS,CSS全面培養人才。
轉載請注明本頁網址:
http://www.snjht.com/jiaocheng/9623.html