MySQL優化

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訪問服務。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章