1 設計優化
1.1 字段設計
1.儘量使用整型表示字符串。
2.decimal不會損失精度,存儲空間會隨數據的增大而增大。double佔用固定空間,較大數的存儲會損失精度。
3.儘可能選擇小的數據類型和指定短的長度。
4.儘可能使用 not null。
5.單表字段不宜過多。
1.2 數據庫設計三範式
-
第一範式(1NF):要求數據庫表的每一列都是不可分割的原子數據項。
-
第二範式(2NF):在1NF的基礎上,非碼屬性必須完全依賴於候選碼(在1NF基礎上消除非主屬性對主碼的部分函數依賴)需要確保數據庫表中的每一列都和主鍵相關,而不能只與主鍵的某一部分相關(主要針對聯合主鍵而言)。
-
第三範式(3NF):在2NF基礎上,任何非主屬性不依賴於其它非主屬性(在2NF基礎上消除傳遞依賴)需要確保數據表中的每一列數據都和主鍵直接相關,而不能間接相關。
1.3 存儲引擎選擇
1.3.1 存儲差異
MyISAM | Innodb | |
---|---|---|
文件格式 | 數據.MYD和索引.MYI分別存儲 | 數據和索引集中存儲.ibd |
文件能否移動 | 能,一張表對應.frm、MYD、MYI文件 | 否,關聯的還有data下文件 |
記錄存儲順序 | 按記錄插入順序保存 | 按主鍵大小有序插入 |
空間碎片 | 產生 | 不產生 |
事務 | 不支持 | 支持 |
外健 | 不支持 | 支持 |
鎖支持 | 表級鎖定 | 行級鎖定,表級鎖定 |
1.3.2 選擇依據
MyISAM:以讀寫插入爲主的應用程序,比如博客系統、新聞門戶網站。
Innodb:更新(刪除)操作頻率也高,或者要保證數據的完整性;併發量高,支持事務和外鍵保證數據完整性。比如OA自動化辦公系統。
2 索引優化
2.1 概念
關鍵字與數據的映射關係稱爲索引(包含關鍵字和對應的記錄在磁盤中的地址)。
2.2 索引類型
-
普通索引:對關鍵字沒有限制
-
唯一索引:要求記錄提供的關鍵字不能重複
-
主鍵索引:要求關鍵字唯一且不爲null
2.3 索引的存儲結構
-
BTree
多路查找平衡樹,BTree的一個node可以存儲多個關鍵字,node的大小取決於計算機的文件系統,因此我們可以通過減小索引字段的長度使結點存儲更多的關鍵字。
-
B+Tree聚簇結構
在MySQL中,僅僅只有
Innodb
的主鍵索引爲聚簇結構,其它的索引包括Innodb
的非主鍵索引都是典型的BTree結構。 -
哈希索引
在索引被載入內存時,使用哈希結構來存儲。
2.4 創建索引
-
創建表之後建立索引
-- 更改表結構alter table user_index-- 創建一個first_name和last_name的複合索引,並命名爲nameadd key name (first_name,last_name),-- 創建一個id_card的唯一索引,默認以字段名作爲索引名add UNIQUE KEY (id_card),-- 雞肋,全文索引不支持中文add FULLTEXT KEY (information);
-
創建表時指定索引
CREATE TABLE user_index2 ( id INT auto_increment PRIMARY KEY, first_name VARCHAR (16), last_name VARCHAR (16), id_card VARCHAR (18), information text, KEY name (first_name, last_name), FULLTEXT KEY (information), UNIQUE KEY (id_card));
2.5 索引優化
可以通過explain selelct
來分析SQL語句執行前的執行計劃
-
建立基礎索引:在
where、order by、join
字段上建立索引。 -
優化,組合索引:基於業務邏輯
-
如果條件經常性出現在一起,那麼可以考慮將多字段索引升級爲複合索引
-
如果通過增加個別字段的索引,就可以出現索引覆蓋,那麼可以考慮爲該字段建立索引
-
查詢時,不常用到的索引,應該刪除掉
簡要解釋下explain各個字段的含義
-
id : 表示SQL執行的順序的標識,SQL從大到小的執行
-
select_type:表示查詢中每個select子句的類型
-
table:顯示這一行的數據是關於哪張表的,有時不是真實的表名字
-
type:表示MySQL在表中找到所需行的方式,又稱“訪問類型”。常用的類型有:ALL, index, range, ref, eq_ref, const, system, NULL(從左到右,性能從差到好)
-
possible_keys:指出MySQL能使用哪個索引在表中找到記錄,查詢涉及到的字段上若存在索引,則該索引將被列出,但不一定被查詢使用
-
Key:key列顯示MySQL實際決定使用的鍵(索引),如果沒有選擇索引,鍵是NULL。
-
key_len:表示索引中使用的字節數,可通過該列計算查詢中使用的索引的長度(key_len顯示的值爲索引字段的最大可能長度,並非實際使用長度,即key_len是根據表定義計算而得,不是通過表內檢索出的)
-
ref:表示上述表的連接匹配條件,即哪些列或常量被用於查找索引列上的值
-
rows:表示MySQL根據表統計信息及索引選用情況,估算的找到所需的記錄所需要讀取的行數,理論上行數越少,查詢性能越好
-
Extra:該列包含MySQL解決查詢的詳細信息
2.6 索引失效的場景
1.當語句中帶有or的時候 即使有索引也會失效。
select * from USER where name='xzz' or age=16;
2.當語句索引 like %在前面的時候索引失效(注意:如果上句爲 like ‘some%’此時索引是生效的)。
select * from USER where name like '%xzz' ;
3.如果列類型是字符串,那一定要在條件中將數據使用引號引用起來,否則不使用索引。
select * from USER where name=123;
4.mysql聯合索引有最左原則,需要按照順序。將name和age設置爲聯合索引。
select * from USER where age=11 and name='xzz';
5.在索引上進行計算操作。
select * from USER where age-1>11;
6.負向查詢不能使用索引
select name from user where id not in (1,3,4);
3 SQL語句優化
3.1 sql優化
-
數據庫導入大量數據時,可以先禁用索引和約束
alter table table-name disable keys
,導入後開啓alter table table-name enable keys
。 -
limit offset,rows 儘量保證不要出現大的
offset
,比如limit 10000,10
相當於對已查詢出來的行數棄掉前10000
行後再取10
行。 -
order by 不要用rand(),效率低。在應用程序中,將隨機的主鍵生成好,去數據庫中利用主鍵檢索。
-
select() count()少用*,如果確定只查詢一條數據,可以用limit 1。
3.2 慢查詢日誌
用於記錄執行時間超過某個臨界值的SQL日誌,用於快速定位慢查詢,爲我們的優化做參考。
3.2.1 開啓慢查詢日誌
配置項:slow_query_log
可以使用show variables like ‘slov_query_log’
查看是否開啓,如果狀態值爲OFF
,可以使用set GLOBAL slow_query_log = on
來開啓,它會在datadir
下產生一個xxx-slow.log
的文件。
3.2.2 設置臨界時間
配置項:long_query_time
查看:show VARIABLES like 'long_query_time'
,單位秒
設置:set long_query_time=0.5
實操時應該從長時間設置到短的時間,即將最慢的SQL優化掉
3.2.3 查看日誌
一旦SQL超過了我們設置的臨界時間就會被記錄到xxx-slow.log
中
3.3 profile信息
3.3.1 查看狀態,並開啓
show variables like 'profiling';--查看狀態
set profiling=on;--開啓
show profiles;--查看profile信息
3.3.2 通過ID查看某條sql所有詳細步驟的時間
通過show profiles;
查找到ID,使用show profile for query Query_ID
查詢經過了哪些步驟,各消耗了多場時間。
4 橫向擴展(MySQL集羣、負載均衡、讀寫分離)
4.1 數據庫分區
當數據量較大時(一般千萬條記錄級別以上),MySQL的性能就會開始下降,這時我們就需要將數據分散到多組存儲文件,保證其單個文件的執行效率。
4.1.1 分區管理語法
-
range/list
alter table article_range add partition( partition p201811 values less than (1543593599) -- select UNIX_TIMESTAMP('2018-11-30 23:59:59') -- more ); alter table article_range drop PARTITION p201808;--刪除分區後,分區中原有的數據也會隨之刪除!
-
key/hash
alter table article_key add partition partitions 4;--新增 alter table article_key coalesce partition 6;--刪除不會刪除數據
4.1.2 mysql分區算法
-
hash(field)
相同的輸入得到相同的輸出。輸出的結果跟輸入是否具有規律無關。僅適用於整型字段。
create table article( id int auto_increment PRIMARY KEY, title varchar(64), content text )PARTITION by HASH(id) PARTITIONS 10;
-
key(field)
和
hash(field)
的性質一樣,只不過key
是處理字符串的,比hash()
多了一步從字符串中計算出一個整型在做取模操作。create table article_key( id int auto_increment, title varchar(64), content text, PRIMARY KEY (id,title) -- 要求分區依據字段必須是主鍵的一部分 )PARTITION by KEY(title) PARTITIONS 10;
-
range算法
是一種條件分區算法,按照數據大小範圍分區(將數據使用某種條件,分散到不同的分區中)。
create table article_range( id int auto_increment, title varchar(64), content text, created_time int, -- 發佈時間到1970-1-1的毫秒數 PRIMARY KEY (id,created_time) -- 要求分區依據字段必須是主鍵的一部分 )charset=utf8PARTITION BY RANGE(created_time)( PARTITION p201808 VALUES less than (1535731199), -- select UNIX_TIMESTAMP('2018-8-31 23:59:59') PARTITION p201809 VALUES less than (1538323199), -- 2018-9-30 23:59:59 PARTITION p201810 VALUES less than (1541001599) -- 2018-10-31 23:59:59 );
-
list算法
也是一種條件分區,按照列表值分區(
in (值列表)
)。create table article_list( id int auto_increment, title varchar(64), content text, status TINYINT(1),-- 文章狀態:0-草稿,1-完成但未發佈,2-已發佈 PRIMARY KEY (id,status) -- 要求分區依據字段必須是主鍵的一部分 )charset=utf8PARTITION BY list(status)( PARTITION writing values in(0,1), -- 未發佈的放在一個分區 PARTITION published values in (2) -- 已發佈的放在一個分區 ); insert into article_list values(null,'mysql優化','內容示例',0); flush tables;
4.2 大表分表
-
垂直分表: 根據數據庫裏面數據表的相關性進行拆分。 例如,用戶表中既有用戶的登錄信息又有用戶的基本信息,可以將用戶表拆分成兩個單獨的表,甚至放到單獨的庫做分庫。簡單來說垂直拆分是指數據表列的拆分,把一張列比較多的表拆分爲多張表。
-
垂直拆分的優點: 可以使得列數據變小,在查詢時減少讀取的Block數,減少I/O次數。此外,垂直分區可以簡化表的結構,易於維護。
-
垂直拆分的缺點: 主鍵會出現冗餘,需要管理冗餘列,並會引起Join操作,可以通過在應用層進行Join來解決。此外,垂直分區會讓事務變得更加複雜;
-
-
水平分表: 保持數據表結構不變,通過某種策略存儲數據分片。這樣每一片數據分散到不同的表或者庫中,達到了分佈式的目的。 水平拆分可以支撐非常大的數據量。 數據庫分片的兩種常見方案:
-
客戶端代理: 分片邏輯在應用端,封裝在jar包中,通過修改或者封裝JDBC層來實現。 噹噹網的 Sharding-JDBC 、阿里的TDDL是兩種比較常用的實現。
-
中間件代理: 在應用和數據中間加了一個代理層。分片邏輯統一維護在中間件服務中。 我們現在談的 Mycat 、360的Atlas、網易的DDB等等都是這種架構的實現。
-
4.3 集羣
單中心雙機的常見方案有以下這些:
-
一主一備的架構(主備式)
將數據庫部署到兩臺機器,其中一臺機器(代號A)作爲日常提供數據讀寫服務的機器,稱爲「主機」。另外一臺機器(代號B)並不提供線上服務,但會實時的將「主機」的數據同步過來,稱爲「備機」。一旦「主機」出了故障,通過人工的方式,手動的將「主機」踢下線,將「備機」改爲「主機」來繼續提供服務。
缺點:人工干預。
-
一主一從的架構(主從式)
主從式架構中的「從機」雖然也在隨時隨刻提供服務,但是它只提供「讀」服務,並不提供「寫」服務。「主機」會實時的將線上數據同步到「從機」,以保證「從機」能夠正常的提供讀操作。
主從雙機自動切換: 當主機出現故障後,從機能夠自動檢測發現。同時從機將自己迅速切換爲主機,將原來的主機立即下線服務,或轉換爲從機狀態。
缺點:數據同步延遲。
-
互爲主從的架構(主主式)
互爲主從的架構是指兩臺機器自己都是主機,並且也都是作爲對方的從機。兩臺機器都提供完整的讀寫服務,因此無需切換,客戶機在調用的時候隨機挑選一臺即可,當其中一臺宕機了,另外一臺還可以繼續服務。
缺點:雙向複製,數據延遲或丟失。
4.4 讀寫分離
讀寫分離是依賴於主從複製,而主從複製又是爲讀寫分離服務的。因爲主從複製要求slave
不能寫只能讀(如果對slave
執行寫操作,那麼show slave status
將會呈現Slave_SQL_Running=NO
,此時你需要按照前面提到的手動同步一下slave
)。
4.4.1 定義兩種連接
就像我們在學JDBC時定義的DataBase
一樣,我們可以抽取出ReadDataBase,WriteDataBase implements DataBase
,但是這種方式無法利用優秀的線程池技術如DruidDataSource
幫我們管理連接,也無法利用Spring AOP
讓連接對DAO
層透明。
4.4.2 使用Spring AOP
在調用DAO接口時根據接口方法命名規範(增addXXX/createXXX
、刪deleteXX/removeXXX
、改updateXXXX
、查selectXX/findXXX/getXX/queryXXX
)動態地選擇數據源(讀數據源對應連接master
而寫數據源對應連接slave
),那麼就可以做到讀寫分離了。(可以參考:Spring 動態切換、添加數據源實現以及源碼淺析)
4.5 負載均衡
4.5.1 負載均衡算法
-
輪詢
-
加權輪詢:按照處理能力來加權
-
負載分配:依據當前的空閒狀態(但是測試每個節點的內存使用率、CPU利用率等,再做比較選出最閒的那個,效率太低)
4.5.2 高可用
在服務器架構時,爲了保證服務器7x24不宕機在線狀態,需要爲每臺單點服務器(由一臺服務器提供服務的服務器,如寫服務器、數據庫中間件)提供冗餘機。(推薦:mysql+mycat搭建穩定高可用集羣)
對於寫服務器來說,需要提供一臺同樣的寫-冗餘服務器,當寫服務器健康時(寫-冗餘通過心跳檢測),寫-冗餘作爲一個從機的角色複製寫服務器的內容與其做一個同步;當寫服務器宕機時,寫-冗餘服務器便頂上來作爲寫服務器繼續提供服務。對外界來說這個處理過程是透明的,即外界僅通過一個IP訪問服務。