參考:https://www.cnblogs.com/lfs2640666960/p/11116962.html
https://blog.csdn.net/cai13070139328/article/details/100099110
數據的讀寫分離只是解決了訪問的壓力,但是存儲的壓力沒有解決。
要想解決存儲的壓力就要引入分庫分表。
分庫:
分庫就是現在你有一個數據庫服務器,數據庫中有兩張表分別是用戶表和訂單表。如果要分庫的話現在需要兩台機器,每個機器上安裝一台數據庫,一台機器上的數據庫放用戶表,一台機器上的數據庫放訂單表。這樣存儲壓力就分擔到兩個服務器上了。
分庫帶來的問題:
聯表查詢問題,也就是join,之前在一個數據庫里面可以用上join,用一條SQL語句就可以聯表查詢得到想要的結果,現在分為多個數據庫join用不上了。比如要查注冊時間在2019年之后用戶的訂單信息,你就需要先去數據庫A中用戶表查詢注冊在2019年之后的信息,然后得到用戶id,再拿這些id去數據庫B訂單表中查詢訂單信息,然后再拼接這些信息返回,所以總體多寫了一些代碼。
事務問題,現在不同的數據庫事務就不是以前那個簡單的本地事務了,而是分布式事務了,而引入分布式事務也提高了系統的復雜性,並且有些效率不高還會影響性能,例如Mysql XA,或者基於消息中間件實現的分布式事務。
分表:
如果一張表里面的數據量過大,會嚴重影響性能,這時候就要考慮分表。
垂直分表:將列進行拆分,將那些不常用的列分到一張表,常用的分到一張表,這樣每張表大小就會變小。垂直分表適合表中存在大量不常用的表。
垂直分表的影響就是之前只要一個查詢的,現在需要兩次查詢才能拿到分表之前的用戶表信息。
水平分表:水平分表適合行數很多的表,例如超過了5000萬,有些場景可能超過1000萬就要分表了。
水平分表帶來了路由問題,也就是按照什么原則分,可以按id范圍、id hash等,或者搞一張表單獨存放路由關系,不過,每次查詢都得查兩次如 果路由表太大,那么路由表又會成為瓶頸。
帶來的查詢問題:比如要查詢注冊時間最早的前100名用戶,這就等於你得在水平分的每一張表都order by一下注冊時間,並且取100個,然后再 把每個表的100個結果對比一下得到最終結果。 整個操作變的復雜了,以前只需要1個order by操作,現在是多個order by。如果分了20個表,那 么20個order by可能很耗時,串行執行則更慢。
分庫分表的實現:
可以在客戶端代碼里面做,也可以使用中間件,這樣客戶端無感知。分庫分表中間件可以使用Mycat。
參考:https://blog.csdn.net/wt077521/article/details/80469015
https://blog.csdn.net/weixin_38319645/article/details/81537849
分庫分表分區 怎么選:
集群擴容:
表的設計:
表字段避免null值出現,null值很難查詢優化且占用額外的索引空間,推薦默認數字0代替null。
盡量使用INT而非BIGINT,如果非負則加上UNSIGNED,當然能使用TINYINT、SMALLINT、MEDIUM_INT更好。
使用枚舉或者整數代替字符串類型
盡量使用TIMESTAMP而非DATATIME
單表不要有太多字段,建議在20以內
用整形來存IP
索引:
索引並不是越多越好,要根據查詢有針對性的創建,考慮在WHERE和ORDER BY命令上涉及的列建立索引,可根據EXPLAIN來查看是否用了索引還是全表掃描
應盡量避免在WHERE子句中對字段進行NULL值判斷,否則將導致引擎放棄使用索引而進行全表掃描
值分布很稀少的字段不適合建立索引,例如:性別
字符字段只建前綴索引
字符字段最好不要做主鍵
不用外鍵,由程序保證約束
盡量不用UNIQUE,由程序保證約束
使用多列索引時注意順序和查詢條件保持一致,同時刪除不必要的單列索引
合適的數據類型:
使用可存下數據的最小的數據類型
使用簡單的數據類型
使用合理的字段屬性長度,固定長度的表會更快
盡可能使用not null定義字段
盡量少用text,非用不可最好分表
選擇合適的索引列:
查詢頻繁的列,在where、group by、order by、on從句中出現的列
where條件中<、<=、=、>、>=、between、in 以及 like 字符串+通配符(%)出現的列
長度小的列,索引字段越小越好,因為數據庫的存儲單位是頁,一頁中能存下的數據越多越好
離散度大的列,放在聯合索引前面。查看離散度,通過統計不同的列值來實現,count越大,離散程度越高
單庫的性能優化:
SQL的優化:
使用limit對查詢結果的記錄進行限定
避免select *, 將需要查找的字段列出來
使用連接( join ) 來代替子查詢
拆分大的delete 或 insert 語句
可通過開啟慢查詢日志來找出較慢的SQL
不做列運算,select id where age + 1 = 10,任何對列的操作都將導致表掃描,它包括數據庫教程函數、計算表達式等等,查詢時要盡可能將操作移至等號右邊。
sql語句盡可能簡單,一條sql只能在一個cpu運算,大語句拆成小語句,減少鎖的時間,一條大的sql可以堵死整個庫
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來分頁,每頁數量也不要太大
分區:
分區是一種簡單的水平拆分,用戶需要在建表的時候加上分區參數,對應用是透明的無需修改代碼
對用戶來說,分區表是一個獨立的邏輯表,但是底層由多個物理子表組成,實現分區的代碼實際上是通過對一組底層表的對象封裝,但對SQL層來說是一個完全封裝底層的黑盒子。mysql實現分區的方式也意味着索引也是按照分區的子表定義,沒有全局索引
用戶的SQL語句是需要針對分區表做優化,SQL條件中要帶上分區條件的列,從而使查詢定位到少量的分區上,否則就會掃描全部分區,可以通過EXPLAIN PARTITIONS 來查看某條SQL語句會落在哪些分區上,從而進行SQL優化
分區的好處:
可以讓單表存儲更多的數據
分區表的數據更容易維護,可以通過清除整個分區批量刪除大量數據,也可以增加新的分區來支持新插入的數據。另外還可以對一個獨立的分區進行優化、檢查、修復等操作
部分查詢能夠從查詢條件上確定只落在少數分區上,速度會很快
分區表的數據還可以分布在不同的物理設備上,從而高效利用硬件設備
可以使用分區表來避免某些特殊瓶頸,例如InnoDB單個索引的互斥訪問、ext3文件系統的inode競爭
可以備份和恢復單個分區
分區的限制和缺點:
一個表只能有1024個分區
如果分區字段中有主鍵或者唯一索引的列,那么所有主鍵列和唯一索引列都必須包含進來
分區表無法使用外鍵約束
NULL值會使分區過濾無效
所有分區必須使用相同的存儲引擎
分區的類型:
RANGE分區:基於屬於一個給定連續區間的列值,把多行分配給分區
LIST分區:類似於按RANGE分區,區別在於LIST分區是基於列值匹配一個離散值集合中的某個值來進行選擇
HASH分區:基於用戶定義的表達式的返回值來進行選擇的分區,該表達式使用將要插入到表中的這些行的列值進行計算,這個函數可以包含Mysql中有效的、產生非負整數值的任何表達式
KEY分區:類似於按HASH分區,區別在於KEY分區只支持計算一列或者多列,且Mysql服務器提供其自身的哈希函數,必須有一列或者多列包含整數值
按上述的優化方案優化后,還是查詢卡死,則可以考慮分表了,把一次查詢分成多次查詢,然后把結果組合返回給用戶。