天天看點

MySQL常用優化指南,及大表優化思路(值得收藏)

雲栖号資訊:【 點選檢視更多行業資訊

在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!

當MySQL單表記錄數過大時,增删改查性能都會急劇下降

單表優化

除非單表資料未來會一直不斷上漲,否則不要一開始就考慮拆分,拆分會帶來邏輯、部署、運維的各種複雜度,一般以整型值為主的表在千萬級以下,字元串為主的表在五百萬以下是沒有太大問題的。

而事實上很多時候 MySQL 單表的性能依然有不少優化空間,甚至能正常支撐千萬級以上的資料量。

字段

  • 盡量使用 TINYINT、 SMALLINT、 MEDIUM_INT 作為整數類型而非 INT,如果非負則加上 UNSIGNED
  • VARCHAR 的長度隻配置設定真正需要的空間
  • 使用枚舉或整數代替字元串類型
  • 盡量使用 TIMESTAMP 而非 DATETIME
  • 單表不要有太多字段,建議在 20 以内
  • 避免使用 NULL 字段,很難查詢優化且占用額外索引空間
  • 用整型來存 IP

索引

  • 索引并不是越多越好,要根據查詢有針對性的建立,考慮在 WHERE 和 ORDER BY
  • 指令上涉及的列建立索引,可根據 EXPLAIN 來檢視是否用了索引還是全表掃描
  • 應盡量避免在 WHERE 子句中對字段進行 NULL 值判斷,否則将導緻引擎放棄使用索引而進行全表掃描
  • 值分布很稀少的字段不适合建索引,例如"性别"這種隻有兩三個值的字段
  • 字元字段隻建字首索引
  • 字元字段最好不要做主鍵
  • 不用外鍵,由程式保證限制
  • 盡量不用 UNIQUE,由程式保證限制
  • 使用多列索引時主意順序和查詢條件保持一緻,同時删除不必要的單列索引

查詢SQL

  • 可通過開啟慢查詢日志來找出較慢的 SQL
  • 不做列運算:SELECT id WHERE age+1=10,任何對列的操作都将導緻表掃描,它包括資料庫教程函數、計算表達式等等,查詢時要盡可能将操作移至等号右邊
  • sql 語句盡可能簡單:一條 sql 隻能在一個 cpu 運算;大語句拆小語句,減少鎖時間;一條大sql 可以堵死整個庫
  • 不用 SELECT *
  • OR 改寫成 IN:OR 的效率是 n 級别, IN 的效率是 log(n) 級别,IN 的個數建議控制在 200 以内
  • 不用函數和觸發器,在應用程式實作
  • 避免 %xxx 式查詢
  • 少用 JOIN
  • 使用同類型進行比較,比如用 '123' 和 '123' 比, 123 和 123 比
  • 盡量避免在 WHERE 子句中使用 != 或 <> 操作符,否則将引擎放棄使用索引而進行全表掃描
  • 對于連續數值,使用 BETWEEN 不用 IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5
  • 清單資料不要拿全表,要使用 LIMIT 來分頁,每頁數量也不要太大

引擎

目前廣泛使用的是 MyISAM 和 InnoDB 兩種引擎:

MyISAM

MyISAM 引擎是 MySQL 5.1 及之前版本的預設引擎,它的特點是:

  • 不支援行鎖,讀取時對需要讀到的所有表加鎖,寫入時則對表加排它鎖
  • 不支援事務
  • 不支援外鍵
  • 不支援崩潰後的安全恢複
  • 在表有讀取查詢的同時,支援往表中插入新紀錄
  • 支援 BLOB 和 TEXT 的前 500 個字元索引,支援全文索引
  • 支援延遲更新索引,極大提升寫入性能
  • 對于不會進行修改的表,支援壓縮表,極大減少磁盤空間占用

InnoDB

InnoDB 在 MySQL 5.5 後成為預設索引,它的特點是:

  • 支援行鎖,采用 MVCC 來支援高并發
  • 支援事務
  • 支援外鍵
  • 支援崩潰後的安全恢複
  • 不支援全文索引(5.6.4之後版本逐漸開始支援)

總體來講,MyISAM 适合 SELECT 密集型的表,而 InnoDB 适合 INSERT 和 UPDATE 密集型的表

系統調優參數

可以使用下面幾個工具來做基準測試:

  • sysbench:一個子產品化,跨平台以及多線程的性能測試工具
  • iibench-mysql:基于 Java 的 MySQL/Percona/MariaDB 索引進行插入性能測試工具
  • tpcc-mysql:Percona 開發的 TPC-C 測試工具

具體的調優參數内容較多,具體可參考官方文檔,這裡介紹一些比較重要的參數:

  • backlog:backlog 值指出在 MySQL 暫時停止回答新請求之前的短時間内多少個請求可以被存在堆棧中。也就是說,如果 MySql 的連接配接資料達到 maxconnections 時,新來的請求将會被存在堆棧中,以等待某一連接配接釋放資源,該堆棧的數量即 backlog,如果等待連接配接的數量超過back_log,将不被授予連接配接資源。可以從預設的 50 升至 500
  • wait_timeout:資料庫連接配接閑置時間,閑置連接配接會占用記憶體資源。可以從預設的 8 小時減到半小時
  • maxuserconnection:最大連接配接數,預設為 0 無上限,最好設一個合理上限
  • thread_concurrency:并發線程數,設為 CPU 核數的兩倍
  • skipnameresolve:禁止對外部連接配接進行 DNS 解析,消除 DNS 解析時間,但需要所有遠端主機用 IP 通路
  • keybuffersize:索引塊的緩存大小,增加會提升索引處理速度,對 MyISAM 表性能影響最大。對于記憶體 4G 左右,可設為 256M 或 384M,通過查詢 show status like'key_read%',保證 key_reads / key_read_requests 在 0.1% 以下最好
  • innodbbufferpool_size:緩存資料塊和索引塊,對 InnoDB 表性能影響最大。通過查詢 show status like'Innodb_buffer_pool_read%',保證 (Innodb_buffer_pool_read_requests –

    Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests 越高越好

  • innodbadditionalmempoolsize:InnoDB 存儲引擎用來存放資料字典資訊以及一些内部資料結構的記憶體空間大小,當資料庫對象非常多的時候,适當調整該參數的大小以確定所有資料都能存放在記憶體中提高通路效率,當過小的時候,MySQL 會記錄 Warning 資訊到資料庫的錯誤日志中,這時就需要該調整這個參數大小
  • innodblogbuffer_size:InnoDB 存儲引擎的事務日志所使用的緩沖區,一般來說不建議超過

    32MB

  • querycachesize:緩存 MySQL 中的 ResultSet,也就是一條 SQL 語句執行的結果集,是以僅僅隻能針對 select 語句。當某個表的資料有任何任何變化,都會導緻所有引用了該表的select 語句在 Query Cache 中的緩存資料失效。是以,當我們的資料變化非常頻繁的情況下,使用 Query Cache 可能會得不償失。根據命中率 (Qcache_hits / (Qcache_hits + Qcache_inserts) * 100)) 進行調整,一般不建議太大,256MB 可能已經差不多了,大型的配置型靜态資料可适當調大. 可以通過指令 show status like'Qcache_%' 檢視目前系統 Query Catch 使用大小
  • readbuffersize:MySql 讀入緩沖區大小。對表進行順序掃描的請求将配置設定一個讀入緩沖區,MySql 會為它配置設定一段記憶體緩沖區。如果對表的順序掃描請求非常頻繁,可以通過增加該變量值以及記憶體緩沖區大小提高其性能
  • sortbuffersize:MySql 執行排序使用的緩沖大小。如果想要增加 ORDER BY 的速度,首先看是否可以讓 MySQL 使用索引而不是額外的排序階段。如果不能,可以嘗試增加 sortbuffersize 變量的大小
  • readrndbuffer_size:MySql 的随機讀緩沖區大小。當按任意順序讀取行時(例如,按照排序順序),将配置設定一個随機讀緩存區。進行排序查詢時,MySql 會首先掃描一遍該緩沖,以避免磁盤搜尋,提高查詢速度,如果需要排序大量資料,可适當調高該值。但 MySql 會為每個客戶連接配接發放該緩沖空間,是以應盡量适當設定該值,以避免記憶體開銷過大
  • record_buffer:每個進行一個順序掃描的線程為其掃描的每張表配置設定這個大小的一個緩沖區。如果你做很多順序掃描,可能想要增加該值

    threadcachesize:儲存目前沒有與連接配接關聯但是準備為後面新的連接配接服務的線程,可以快速響應連接配接的線程請求而無需建立新的

  • tablecache:類似于 threadcache_size,但用來緩存表檔案,對 InnoDB 效果不大,主要用于 MyISAM

更新硬體

Scale up,這個不多說了,根據 MySQL 是 CPU 密集型還是 I/O 密集型,通過提升 CPU 和記憶體、使用 SSD,都能顯著提升 MySQL 性能。

讀寫分離

也是目前常用的優化,從庫讀主庫寫,一般不要采用雙主或多主引入很多複雜性,盡量采用文中的其他方案來提高性能。

同時目前很多拆分的解決方案同時也兼顧考慮了讀寫分離。讀寫分離:手把手教你實作MySQL讀寫分離

緩存

緩存可以發生在這些層次:

  • MySQL 内部:在系統調優參數介紹了相關設定
  • 資料通路層:比如 MyBatis 針對 SQL 語句做緩存,而 Hibernate 可以精确到單個記錄,這裡緩存的對象主要是持久化對象 PersistenceObject
  • 應用服務層:這裡可以通過程式設計手段對緩存做到更精準的控制和更多的實作政策,這裡緩存的對象是資料傳輸對象 DataTransferObject(DTO)
  • Web 層:針對 web 頁面做緩存
  • 浏覽器用戶端:使用者端的緩存

可以根據實際情況在一個層次或多個層次結合加入緩存。緩存擴充:緩存常見問題及解決方案

這裡重點介紹下服務層的緩存實作,目前主要有兩種方式:

  • 直寫式(Write Through):在資料寫入資料庫後,同時更新緩存,維持資料庫與緩存的一緻性。這也是目前大多數應用緩存架構如 Spring Cache 的工作方式。這種實作非常簡單,同步好,但效率一般。
  • 回寫式(Write Back):當有資料要寫入資料庫時,隻會更新緩存,然後異步批量的将緩存資料同步到資料庫上。這種實作比較複雜,需要較多的應用邏輯,同時可能會産生資料庫與緩存的不同步,但效率非常高。

表分區

MySQL 在 5.1 版引入的分區是一種簡單的水準拆分,使用者需要在建表的時候加上分區參數,對應用是透明的無需修改代碼

對使用者來說,分區表是一個獨立的邏輯表,但是底層由多個實體子表組成,實作分區的代碼實際上是通過對一組底層表的對象封裝,但對 SQL 層來說是一個完全封裝底層的黑盒子。MySQL 實作分區的方式也意味着索引也是按照分區的子表定義,沒有全局索引。

MySQL常用優化指南,及大表優化思路(值得收藏)

使用者的 SQL 語句是需要針對分區表做優化,SQL 條件中要帶上分區條件的列,進而使查詢定位到少量的分區上,否則就會掃描全部分區,可以通過 EXPLAIN PARTITIONS 來檢視某條SQL 語句會落在那些分區上,進而進行 SQL 優化,如下圖 5 條記錄落在兩個分區上:

MySQL常用優化指南,及大表優化思路(值得收藏)

分區的好處是:

  • 可以讓單表存儲更多的資料
  • 分區表的資料更容易維護,可以通過清楚整個分區批量删除大量資料,也可以增加新的分區來支援新插入的資料。另外,還可以對一個獨立- - 分區進行優化、檢查、修複等操作
  • 部分查詢能夠從查詢條件确定隻落在少數分區上,速度會很快
  • 分區表的資料還可以分布在不同的實體裝置上,進而高效利用多個硬體裝置
  • 可以使用分區表賴避免某些特殊瓶頸,例如 InnoDB 單個索引的互斥通路、ext3 檔案系統的
  1. 鎖競争
  • 可以備份和恢複單個分區

分區的限制和缺點:

  • 一個表最多隻能有 1024 個分區
  • 如果分區字段中有主鍵或者唯一索引的列,那麼所有主鍵列和唯一索引列都必須包含進來
  • 分區表無法使用外鍵限制
  • NULL 值會使分區過濾無效
  • 所有分區必須使用相同的存儲引擎

分區的類型:

  • RANGE 分區:基于屬于一個給定連續區間的列值,把多行配置設定給分區
  • LIST 分區:類似于按 RANGE 分區,差別在于 LIST 分區是基于列值比對一個離散值集合中的某個值來進行選擇
  • HASH 分區:基于使用者定義的表達式的傳回值來進行選擇的分區,該表達式使用将要插入到表中的這些行的列值進行計算。這個函數可以包含 MySQL 中有效的、産生非負整數值的任何表達式
  • KEY 分區:類似于按 HASH 分區,差別在于 KEY 分區隻支援計算一列或多列,且 MySQL 伺服器提供其自身的哈希函數。必須有一列或多列包含整數值

分區适合的場景有:

最适合的場景資料的時間序列性比較強,則可以按時間來分區,如下所示:

MySQL常用優化指南,及大表優化思路(值得收藏)

查詢時加上時間範圍條件效率會非常高,同時對于不需要的曆史資料能很容的批量删除。

如果資料有明顯的熱點,而且除了這部分資料,其他資料很少被通路到,那麼可以将熱點資料單獨放在一個分區,讓這個分區的資料能夠有機會都緩存在記憶體中,查詢時隻通路一個很小的分區表,能夠有效使用索引和緩存。索引相關:帶你從頭到尾捋一遍MySQL索引

另外 MySQL 有一種早期的簡單的分區實作 - 合并表(merge table),限制較多且缺乏優化,不建議使用,應該用新的分區機制來替代

垂直拆分

垂直分庫是根據資料庫裡面的資料表的相關性進行拆分。擴充:網際網路公司常用分庫分表方案彙總

比如:一個資料庫裡面既存在使用者資料,又存在訂單資料,那麼垂直拆分可以把使用者資料放到使用者庫、把訂單資料放到訂單庫。

垂直分表是對資料表進行垂直拆分的一種方式,常見的是把一個多字段的大表按常用字段和非常用字段進行拆分,每個表裡面的資料記錄數一般情況下是相同的,隻是字段不一樣,使用主鍵關聯

比如原始的使用者表是:

MySQL常用優化指南,及大表優化思路(值得收藏)

垂直拆分的優點是:

  • 可以使得行資料變小,一個資料塊( Block )就能存放更多的資料,在查詢時就會減少 I/O 次數(每次查詢時讀取的 Block 就少)
  • 可以達到最大化利用 Cache 的目的,具體在垂直拆分的時候可以将不常變的字段放一起,将經常改變的放一起
  • 資料維護簡單

缺點是:

  • 主鍵出現備援,需要管理備援列
  • 會引起表連接配接 JOIN 操作(增加 CPU 開銷)可以通過在業務伺服器上進行 join 來減少資料庫壓力
  • 依然存在單表資料量過大的問題(需要水準拆分)
  • 事務處理複雜

水準拆分

概述

水準拆分是通過某種政策将資料分片來存儲,分庫内分表和分庫兩部分,每片資料會分散到不同的 MySQL 表或庫,達到分布式的效果,能夠支援非常大的資料量。前面的表分區本質上也是一種特殊的庫内分表。

庫内分表,僅僅是單純的解決了單一表資料過大的問題,由于沒有把表的資料分布到不同的機器上,是以對于減輕 MySQL 伺服器的壓力來說,并沒有太大的作用,大家還是競争同一個實體機上的 IO、CPU、網絡,這個就要通過分庫來解決

前面垂直拆分的使用者表如果進行水準拆分,結果是:

MySQL常用優化指南,及大表優化思路(值得收藏)

實際情況中往往會是垂直拆分和水準拆分的結合,即将 Users_A_M 和 Users_N_Z 再拆成 Users 和 UserExtras,這樣一共四張表

水準拆分的優點是:

  • 不存在單庫大資料和高并發的性能瓶頸
  • 應用端改造較少
  • 提高了系統的穩定性和負載能力
  • 分片事務一緻性難以解決
  • 跨節點 Join 性能差,邏輯複雜
  • 資料多次擴充難度跟維護量極大

分片原則

  • 能不分就不分,參考單表優化
  • 分片數量盡量少,分片盡量均勻分布在多個資料結點上,因為一個查詢 SQL 跨分片越多,則總體性能越差,雖然要好于所有資料在一個分片的結果,隻在必要的時候進行擴容,增加分片數量
  • 分片規則需要慎重選擇做好提前規劃,分片規則的選擇,需要考慮資料的增長模式,資料的通路模式,分片關聯性問題,以及分片擴容問題,最近的分片政策為範圍分片,枚舉分片,一緻性 Hash 分片,這幾種分片都有利于擴容
  • 盡量不要在一個事務中的 SQL 跨越多個分片,分布式事務一直是個不好處理的問題
  • 查詢條件盡量優化,盡量避免 Select * 的方式,大量資料結果集下,會消耗大量帶寬和

    CPU 資源,查詢盡量避免傳回大量結果集,并且盡量為頻繁使用的查詢語句建立索引。

  • 通過資料備援和表分區依賴降低跨庫 Join 的可能

這裡特别強調一下分片規則的選擇問題,如果某個表的資料有明顯的時間特征,比如訂單、交易記錄等,則他們通常比較合适用時間範圍分片,因為具有時效性的資料,我們往往關注其近期的資料,查詢條件中往往帶有時間字段進行過濾,比較好的方案是,目前活躍的資料,采用跨度比較短的時間段進行分片,而曆史性的資料,則采用比較長的跨度存儲。

總體上來說,分片的選擇是取決于最頻繁的查詢 SQL 的條件,因為不帶任何 Where 語句的查詢 SQL,會周遊所有的分片,性能相對最差,是以這種 SQL 越多,對系統的影響越大,是以我們要盡量避免這種 SQL 的産生。

解決方案

由于水準拆分牽涉的邏輯比較複雜,目前也有了不少比較成熟的解決方案。這些方案分為兩大類:

  • 用戶端架構
  • 代理架構

通過修改資料通路層,如 JDBC、Data Source、MyBatis,通過配置來管理多個資料源,直連資料庫,并在子產品内完成資料的分片整合,一般以 Jar 包的方式呈現

這是一個用戶端架構的例子:

MySQL常用優化指南,及大表優化思路(值得收藏)

可以看到分片的實作是和應用伺服器在一起的,通過修改 Spring JDBC 層來實作

用戶端架構的優點是:

  • 應用直連資料庫,降低外圍系統依賴所帶來的當機風險
  • 內建成本低,無需額外運維的元件
  • 限于隻能在資料庫通路層上做文章,擴充性一般,對于比較複雜的系統可能會力不從心
  • 将分片邏輯的壓力放在應用伺服器上,造成額外風險

通過獨立的中間件來統一管理所有資料源和資料分片整合,後端資料庫叢集對前端應用程式透明,需要獨立部署和運維代理元件

這是一個代理架構的例子:

MySQL常用優化指南,及大表優化思路(值得收藏)

代理元件為了分流和防止單點,一般以叢集形式存在,同時可能需要 Zookeeper 之類的服務元件來管理

代理架構的優點是:

  • 能夠處理非常複雜的需求,不受資料庫通路層原來實作的限制,擴充性強
  • 對于應用伺服器透明且沒有增加任何額外負載
  • 需部署和運維獨立的代理中間件,成本高
  • 應用需經過代理來連接配接資料庫,網絡上多了一跳,性能有損失且有額外風險

各方案比較

MySQL常用優化指南,及大表優化思路(值得收藏)

如此多的方案,如何進行選擇?可以按以下思路來考慮:

  • 确定是使用代理架構還是用戶端架構。中小型規模或是比較簡單的場景傾向于選擇用戶端架構,複雜場景或大規模系統傾向選擇代理架構
  • 具體功能是否滿足,比如需要跨節點 ORDER BY,那麼支援該功能的優先考慮
  • 不考慮一年内沒有更新的産品,說明開發停滞,甚至無人維護和技術支援
  • 最好按大公司 -> 社群 -> 小公司 -> 個人這樣的出品方順序來選擇
  • 選擇口碑較好的,比如 GitHub 星數、使用者數量品質和使用者回報
  • 開源的優先,往往項目有特殊需求可能需要改動源代碼

按照上述思路,推薦以下選擇:

  • 用戶端架構:ShardingJDBC
  • 代理架構:MyCat 或 Atlas

相容 MySQL 且可水準擴充的資料庫

目前也有一些開源資料庫相容 MySQL 協定,如:

MySQL常用優化指南,及大表優化思路(值得收藏)

NoSQL

在 MySQL 上做 Sharding 是一種戴着鐐铐的跳舞,事實上很多大表本身對 MySQL 這種RDBMS 的需求并不大,并不要求 ACID,可以考慮将這些表遷移到 NoSQL,徹底解決水準擴充問題,例如:

  • 日志類、監控類、統計類資料
  • 非結構化或弱結構化資料
  • 對事務要求不強,且無太多關聯操作的資料

【雲栖号線上課堂】每天都有産品技術專家分享!

課程位址:

https://yqh.aliyun.com/zhibo

立即加入社群,與專家面對面,及時了解課程最新動态!

【雲栖号線上課堂 社群】

https://c.tb.cn/F3.Z8gvnK

原文釋出時間:2020-03-27

本文作者:網際網路架構師

本文來自:“

網際網路架構師 微信公衆号

”,了解相關資訊可以關注“

網際網路架構師