天天看點

MySQL進階(2)

1. MySQL的體系結構概覽

MySQL進階(2)

整個MySQL Server由以下組成

  • Connection Pool : 連接配接池元件
  • Management Services & Utilities : 管理服務和工具元件
  • SQL Interface : SQL接口元件
  • Parser : 查詢分析器元件
  • Optimizer : 優化器元件
  • Caches & Buffers : 緩沖池元件
  • Pluggable Storage Engines : 存儲引擎
  • File System : 檔案系統
  1. 連接配接層

最上層是一些用戶端和連結服務,包含本地sock 通信和大多數基于用戶端/服務端工具實作的類似于 TCP/IP的通信。主要完成一些類似于連接配接處理、授權認證、及相關的安全方案。在該層上引入了線程池的概念,為通過認證安全接入的用戶端提供線程。同樣在該層上可以實作基于SSL的安全連結。伺服器也會為安全接入的每個用戶端驗證它所具有的操作權限。

  1. 服務層

第二層架構主要完成大多數的核心服務功能,如SQL接口,并完成緩存的查詢,SQL的分析和優化,部分内置函數的執行。所有跨存儲引擎的功能也在這一層實作,如 過程、函數等。在該層,伺服器會解析查詢并建立相應的内部解析樹,并對其完成相應的優化如确定表的查詢的順序,是否利用索引等, 最後生成相應的執行操作。如果是select語句,伺服器還會查詢内部的緩存,如果緩存空間足夠大,這樣在解決大量讀操作的環境中能夠很好的提升系統的性能。

  1. 引擎層

存儲引擎層, 存儲引擎真正的負責了MySQL中資料的存儲和提取,伺服器通過API和存儲引擎進行通信。不同的存儲引擎具有不同的功能,這樣我們可以根據自己的需要,來選取合适的存儲引擎。

  1. 存儲層

資料存儲層, 主要是将資料存儲在檔案系統之上,并完成與存儲引擎的互動。

和其他資料庫相比,MySQL有點與衆不同,它的架構可以在多種不同場景中應用并發揮良好作用。主要展現在存儲引擎上,插件式的存儲引擎架構,将查詢處理和其他的系統任務以及資料的存儲提取分離。這種架構可以根據業務的需求和實際需要選擇合适的存儲引擎。

2. 存儲引擎

2.1 概述

與大多數的資料庫不同, MySQL中有一個存儲引擎的概念, 針對不同的存儲需求可以選擇最優的存儲引擎。

​存儲引擎就是存儲資料,建立索引,更新查詢資料等等技術的實作方式 。存儲引擎是基于表的,而不是基于庫的。(即隻能給表指定存儲引擎,而不能給資料庫指定存儲引擎)是以存儲引擎也可被稱為表類型。

​Oracle,SqlServer等資料庫隻有一種存儲引擎。MySQL提供了插件式的存儲引擎架構。是以MySQL存在多種存儲引擎,可以根據需要使用相應引擎,或者編寫存儲引擎。

​ MySQL5.0支援的存儲引擎包含 : InnoDB 、MyISAM 、BDB、MEMORY、MERGE、EXAMPLE、NDB Cluster、ARCHIVE、CSV、BLACKHOLE、FEDERATED等,其中 InnoDB 和 BDB 提供事務安全表,其他存儲引擎是非事務安全表。

查詢目前資料庫支援的存儲引擎 :

建立新表時如果不指定存儲引擎,那麼系統就會使用預設的存儲引擎,MySQL5.5之前的預設存儲引擎是MyISAM,5.5之後就改為了InnoDB。

檢視Mysql資料庫預設的存儲引擎 :

2.2 各種存儲引擎特性

下面重點介紹幾種常用的存儲引擎, 并對比各個存儲引擎之間的差別, 如下表所示 :

特點 InnoDB MyISAM MEMORY MERGE NDB
存儲限制 64TB 沒有
事務安全 支援
鎖機制 行鎖(适合高并發) 表鎖 表鎖 表鎖 行鎖
B樹索引 支援 支援 支援 支援 支援
哈希索引 支援
全文索引 支援(5.6版本之後) 支援
叢集索引 支援
資料索引 支援 支援 支援
索引緩存 支援 支援 支援 支援 支援
資料可壓縮 支援
空間使用 N/A
記憶體使用 中等
批量插入速度
支援外鍵 支援

下面我們将重點介紹最長使用的兩種存儲引擎: InnoDB、MyISAM , 另外兩種 MEMORY、MERGE , 了解即可。

1.InnoDB

​InnoDB存儲引擎是Mysql的預設存儲引擎。InnoDB存儲引擎提供了具有送出、復原、崩潰恢複能力的事務安全。但是對比MyISAM的存儲引擎,InnoDB寫的處理效率差一些,并且會占用更多的磁盤空間以保留資料和索引。

InnoDB存儲引擎不同于其他存儲引擎的特點 :

  • 事務控制
  • 外鍵

MySQL支援外鍵的存儲引擎隻有InnoDB , 在建立外鍵的時候, 要求父表必須有對應的索引 , 子表在建立外鍵的時候, 也會自動的建立對應的索引。

​下面兩張表中 , country_innodb是父表 , country_id為主鍵索引,city_innodb表是子表,country_id字段為外鍵,對應于country_innodb表的主鍵country_id 。

CREATE TABLE country_innodb(
	country_id INT NOT NULL AUTO_INCREMENT,
    country_name VARCHAR(100) NOT NULL,
    PRIMARY KEY(country_id)
)ENGINE=INNODB DEFAULT CHARSET=utf8;


CREATE TABLE city_innodb(
	city_id INT NOT NULL AUTO_INCREMENT,
    city_name VARCHAR(50) NOT NULL,
    country_id INT NOT NULL,
    PRIMARY KEY(city_id),
    KEY idx_fk_country_id(country_id),
    CONSTRAINT `fk_city_country` FOREIGN KEY(country_id) REFERENCES country_innodb(country_id) 
    ON DELETE RESTRICT ON UPDATE CASCADE
)ENGINE=INNODB DEFAULT CHARSET=utf8;
           

在建立索引時, 可以指定在删除、更新父表時,對子表進行的相應操作,包括 RESTRICT、CASCADE、SET NULL 和 NO ACTION。

RESTRICT和NO ACTION相同, 是指限制在子表有關聯記錄的情況下, 父表不能更新或者删除 —— 拒絕更新、拒絕删除

CASCADE表示父表在更新或者删除時,更新或者删除子表對應的記錄 —— 級聯更新、級聯删除

SET NULL 則表示父表在更新或者删除的時候,子表的對應字段被SET NULL —— 置空操作

針對上面建立的兩個表, 子表的外鍵指定是ON DELETE RESTRICT ON UPDATE CASCADE 方式的, 即拒絕删除和級聯更新那麼在主表删除記錄的時候, 如果子表有對應記錄, 則不允許删除, 主表在更新記錄的時候, 如果子表有對應記錄, 則子表對應更新 。

外鍵資訊的檢視方式:

  • 存儲方式

InnoDB 存儲表和索引有以下兩種方式 :

① 使用共享表空間存儲, 這種方式建立的表的表結構儲存在.frm檔案中, 資料和索引儲存在 innodb_data_home_dir 和 innodb_data_file_path定義的表空間中,可以是多個檔案。

② 使用多表空間存儲, 這種方式建立的表的表結構仍然存在 .frm 檔案中,但是每個表的資料和索引單獨儲存在 .ibd 中。

2. MyISAM

MyISAM 不支援事務、也不支援外鍵,其優勢是通路的速度快,對事務的完整性沒有要求或者以SELECT、INSERT為主的應用基本上都可以使用這個引擎來建立表 。有以下兩個比較重要的特點:

  • 不支援事務
  • 檔案存儲方式

每個MyISAM在磁盤上存儲成3個檔案,其檔案名都和表名相同,但拓展名分别是 :

.frm (存儲表定義)

.MYD(MYData , 存儲資料)

.MYI(MYIndex , 存儲索引)

3. MEMORY

Memory存儲引擎将表的資料存放在記憶體中。每個MEMORY表實際對應一個磁盤檔案,格式是.frm ,該檔案中隻存儲表的結構,而其資料檔案,都是存儲在記憶體中,這樣有利于資料的快速處理,提高整個表的效率。MEMORY 類型的表通路非常地快,因為他的資料是存放在記憶體中的,并且預設使用HASH索引,但是服務一旦關閉,表中的資料就會丢失。

4 MERGE

​MERGE存儲引擎是一組MyISAM表的組合,這些MyISAM表必須結構完全相同,MERGE表本身并沒有存儲資料,對MERGE類型的表可以進行查詢、更新、删除操作,這些操作實際上是對内部的MyISAM表進行的。

對于MERGE類型表的插入操作,是通過 INSERT_METHOD 子句定義插入的表,可以有3個不同的值,使用FIRST 或 LAST 值使得插入操作被相應地作用在第一或者最後一個表上,不定義這個子句或者定義為NO,表示不能對這個MERGE表執行插入操作。

可以對MERGE表進行DROP操作,但是這個操作隻是删除MERGE表的定義,對内部的表是沒有任何影響的。

MySQL進階(2)

下面是一個建立和使用MERGE表的示例 :

  1. 建立3個測試表 order_1990, order_1991, order_all , 其中order_all是前兩個表的MERGE表:
CREATE TABLE order_1990(
	order_id INT ,
	order_money DOUBLE(10,2),
	order_address VARCHAR(50),
	PRIMARY KEY (order_id)
)ENGINE = MYISAM DEFAULT CHARSET=utf8; --需要是MYISAM表


CREATE TABLE order_1991(
	order_id INT ,
	order_money DOUBLE(10,2),
	order_address VARCHAR(50),
	PRIMARY KEY (order_id)
)ENGINE = MYISAM DEFAULT CHARSET=utf8; --需要是MYISAM表


CREATE TABLE order_all(
	order_id INT ,
	order_money DOUBLE(10,2),
	order_address VARCHAR(50),
	PRIMARY KEY (order_id)
)ENGINE = MERGE UNION = (order_1990,order_1991) INSERT_METHOD=LAST DEFAULT CHARSET=utf8; 
-- 定義MERGE表時定義INSERT_METHOD=LAST(FIRST)
           
  1. 分别向兩張表中插入記錄
insert into order_1990 values(1,100.0,'北京');
insert into order_1990 values(2,100.0,'上海');

insert into order_1991 values(10,200.0,'北京');
insert into order_1991 values(11,200.0,'上海');
           
  1. 查詢3張表中的資料
order_1990中的資料 :
MySQL進階(2)
order_1991中的資料 :
MySQL進階(2)
order_all中的資料 :
MySQL進階(2)
  1. 說明

MERGE表本身沒有存儲資料,對MERGE表的各種操作實際上是對内部MYISAM表的操作。是以上例中對MERGE表的查詢實際上是對兩個内部表的查詢。

2.3 存儲引擎的選擇

在選擇存儲引擎時,應該根據應用系統的特點選擇合适的存儲引擎。對于複雜的應用系統,還可以根據實際情況選擇多種存儲引擎進行組合。以下是幾種常用的存儲引擎的使用環境。

  • InnoDB : 是Mysql的預設存儲引擎,用于事務處理應用程式,支援外鍵。如果應用對事務的完整性有比較高的要求,在并發條件下要求資料的一緻性,資料操作除了插入和查詢意外,還包含很多的更新、删除操作,那麼InnoDB存儲引擎是比較合适的選擇。InnoDB存儲引擎除了有效的降低由于删除和更新導緻的鎖定, 還可以確定事務的完整送出和復原,對于類似于計費系統或者财務系統等對資料準确性要求比較高的系統,InnoDB是最合适的選擇。
  • MyISAM : 如果應用是以讀操作和插入操作為主,隻有很少的更新和删除操作,并且對事務的完整性、并發性要求不是很高,那麼選擇這個存儲引擎是非常合适的。
  • MEMORY:将所有資料儲存在RAM中,在需要快速定位記錄和其他類似資料環境下,可以提供幾塊的通路。MEMORY的缺陷就是對表的大小有限制,太大的表無法緩存在記憶體中,其次是要確定表的資料可以恢複,資料庫異常終止後表中的資料是可以恢複的。MEMORY表通常用于更新不太頻繁的小表,用以快速得到通路結果。
  • MERGE:用于将一系列等同的MyISAM表以邏輯方式組合在一起,并作為一個對象引用他們。MERGE表的優點在于可以突破對單個MyISAM表的大小限制,并且通過将不同的表分布在多個磁盤上,可以有效的改善MERGE表的通路效率。這對于存儲諸如資料倉儲等VLDB環境十分合适。

3.SQL優化步驟

在應用的的開發過程中,由于初期資料量小,開發人員寫 SQL 語句時更重視功能上的實作,但是當應用系統正式上線後,随着生産資料量的急劇增長,很多 SQL 語句開始逐漸顯露出性能問題,對生産的影響也越來越大,此時這些有問題的 SQL 語句就成為整個系統性能的瓶頸,是以我們必須要對它們進行優化,本章将詳細介紹在 MySQL 中優化 SQL 語句的方法。

當面對一個有 SQL 性能問題的資料庫時,我們應該從何處入手來進行系統的分析,使得能夠盡快定位問題 SQL 并盡快解決問題。

3.1 檢視SQL執行頻率

MySQL 用戶端連接配接成功後,通過 show [session|global] status 指令可以提供伺服器狀态資訊。show [session|global] status 可以根據需要加上參數“session”或者“global”來顯示 session 級(目前連接配接)的計結果和 global 級(自資料庫上次啟動至今)的統計結果。如果不寫,預設使用參數是“session”。

下面的指令顯示了目前 session 中所有統計參數的值:

MySQL進階(2)
MySQL進階(2)

Com_xxx 表示每個 xxx 語句執行的次數,我們通常比較關心的是以下幾個統計參數。

參數 含義
Com_select 執行 SELECT 操作的次數,一次查詢隻累加 1。
Com_insert 執行 INSERT 操作的次數,對于批量插入的 INSERT 操作,隻累加一次。
Com_update 執行 UPDATE 操作的次數。
Com_delete 執行 DELETE 操作的次數。
Innodb_rows_read InnoDB 引擎表中執行 SELECT 查詢傳回的行數。
Innodb_rows_inserted InnoDB 引擎表中執行 INSERT 操作插入的行數。
Innodb_rows_updated InnoDB 引擎表中執行 UPDATE 操作更新的行數。
Innodb_rows_deleted InnoDB 引擎表中執行 DELETE 操作删除的行數。
Connections 試圖連接配接 MySQL 伺服器的次數。
Uptime 伺服器工作時間。
Slow_queries 慢查詢的次數。
  • Com_*** : 這些參數對于所有存儲引擎的表操作都會進行累計
  • Innodb_*** : 這幾個參數隻是針對InnoDB 存儲引擎的,累加的算法也略有不同
3.2 定位低效率執行SQL

可以通過以下兩種方式定位執行效率較低的 SQL 語句。

  • 慢查詢日志 : 通過慢查詢日志定位那些執行效率較低的 SQL 語句,用–log-slow-queries[=file_name]選項啟動時,mysqld 寫一個包含所有執行時間超過 long_query_time 秒的 SQL 語句的日志檔案。
  • show processlist : 慢查詢日志在查詢結束以後才紀錄,是以在應用反映執行效率出現問題的時候查詢慢查詢日志并不能定位問題,可以使用show processlist指令檢視目前MySQL在進行的線程,包括線程的狀态、是否鎖表等,可以實時地檢視 SQL 的執行情況,同時對一些鎖表操作進行優化。
    MySQL進階(2)
    • id列,使用者登入mysql時,系統配置設定的"connection_id",可以使用函數connection_id()檢視
    • user列,顯示目前使用者。如果不是root,這個指令就隻顯示使用者權限範圍的sql語句
    • host列,顯示這個語句是從哪個ip的哪個端口上發的,可以用來跟蹤出現問題語句的使用者
    • db列,顯示這個程序目前連接配接的是哪個資料庫
    • command列,顯示目前連接配接的執行的指令,一般取值為休眠(sleep),查詢(query),連接配接(connect)等
    • time列,顯示這個狀态持續的時間,機關是秒
    • state列,顯示使用目前連接配接的sql語句的狀态,很重要的列。state描述的是語句執行中的某一個狀态。一個sql語句,以查詢為例,可能需要經過copying to tmp table、sorting result、sending data等狀态才可以完成
    • info列,顯示這個sql語句,是判斷問題語句的一個重要依據
3.3 explain分析執行計劃

通過以上步驟查詢到效率低的 SQL 語句後,可以通過 EXPLAIN 或者 DESC 指令擷取 MySQL 如何執行 SELECT 語句的資訊,包括在 SELECT 語句執行過程中表如何連接配接和連接配接的順序。

查詢SQL語句的執行計劃 :

MySQL進階(2)
MySQL進階(2)
字段 含義
id SELECT 查詢的序列号,是一組數字,表示的是查詢中執行 SELECT 子句或者是操作表的順序。
select_type 表示 SELECT 的類型,常見的取值有 SIMPLE(簡單表,即不使用表連接配接或者子查詢)、PRIMARY(主查詢,即外層的查詢)、UNION(UNION 中的第二個或者後面的查詢語句)、SUBQUERY(子查詢中的第一個 SELECT)等
table 輸出結果集的表
type 表示表的連接配接類型,性能由好到差的連接配接類型為( system —> const -----> eq_ref ------> ref -------> ref_or_null ----> index_merge —> index_subquery -----> range -----> index ------> all )
possible_keys 表示查詢時,可能使用的索引
key 表示實際使用的索引
key_len 索引字段的長度
rows 掃描行的數量
extra 執行情況的說明和描述

3.3.1 資料準備

MySQL進階(2)
CREATE TABLE `t_role` (
  `id` VARCHAR(32) NOT NULL,
  `role_name` VARCHAR(255) DEFAULT NULL,
  `role_code` VARCHAR(255) DEFAULT NULL,
  `description` VARCHAR(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_role_name` (`role_name`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;


CREATE TABLE `t_user` (
  `id` VARCHAR(32) NOT NULL,
  `username` VARCHAR(45) NOT NULL,
  `password` VARCHAR(96) NOT NULL,
  `name` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_user_username` (`username`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;


CREATE TABLE `user_role` (
  `id` INT(11) NOT NULL AUTO_INCREMENT ,
  `user_id` VARCHAR(32) DEFAULT NULL,
  `role_id` VARCHAR(32) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_ur_user_id` (`user_id`),
  KEY `fk_ur_role_id` (`role_id`),
  CONSTRAINT `fk_ur_role_id` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT `fk_ur_user_id` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO `t_user` (`id`, `username`, `password`, `name`) VALUES('1','super','$2a$10$TJ4TmCdK.X4wv/tCqHW14.w70U3CC33CeVncD3SLmyMXMknstqKRe','超級管理者');
INSERT INTO `t_user` (`id`, `username`, `password`, `name`) VALUES('2','admin','$2a$10$TJ4TmCdK.X4wv/tCqHW14.w70U3CC33CeVncD3SLmyMXMknstqKRe','系統管理者');
INSERT INTO `t_user` (`id`, `username`, `password`, `name`) VALUES('3','itcast','$2a$10$8qmaHgUFUAmPR5pOuWhYWOr291WJYjHelUlYn07k5ELF8ZCrW0Cui','test02');
INSERT INTO `t_user` (`id`, `username`, `password`, `name`) VALUES('4','stu1','$2a$10$pLtt2KDAFpwTWLjNsmTEi.oU1yOZyIn9XkziK/y/spH5rftCpUMZa','學生1');
INSERT INTO `t_user` (`id`, `username`, `password`, `name`) VALUES('5','stu2','$2a$10$nxPKkYSez7uz2YQYUnwhR.z57km3yqKn3Hr/p1FR6ZKgc18u.Tvqm','學生2');
INSERT INTO `t_user` (`id`, `username`, `password`, `name`) VALUES('6','t1','$2a$10$TJ4TmCdK.X4wv/tCqHW14.w70U3CC33CeVncD3SLmyMXMknstqKRe','老師1');

INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('5','學生','student','學生');
INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('7','老師','teacher','老師');
INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('8','教學管理者','teachmanager','教學管理者');
INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('9','管理者','admin','管理者');
INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('10','超級管理者','super','超級管理者');

INSERT INTO user_role(id,user_id,role_id) VALUES(NULL, '1', '5'),(NULL, '1', '7'),(NULL, '2', '8'),(NULL, '3', '9'),(NULL, '4', '8'),(NULL, '5', '10') ;
           

【說明】`是 MySQL 的轉義符,避免和 MySQL 的本身的關鍵字沖突,隻要你不在列名、表名中使用 mysql 的保留字或中文,就不需要轉義。所有的資料庫都有類似的設定,不過 MySQL 用的是該符号,通常用來說明其中的内容是資料庫名、表名、字段名,不是關鍵字。

3.3.2 explain 之 id

id 字段是 select 查詢的序列号,是一組數字,表示的是查詢中執行 select 子句或者是操作表的順序。id 情況有三種 :

  1. id 相同表示加載表的順序從上到下排列
MySQL進階(2)
  1. id 不同id值越大,優先級越高,越先被執行
EXPLAIN SELECT * FROM t_role 
WHERE id = (SELECT role_id FROM user_role WHERE user_id = (SELECT id FROM t_user WHERE username = 'stu1'));
           
MySQL進階(2)

即 id 為3的最先執行,id 為1的最後執行

  1. id 有相同,也有不同,同時存在。id相同的可以認為是一組,從上往下順序執行;在所有的組中,id的值越大,優先級越高,越先執行
MySQL進階(2)

3.3.3 explain 之 select_type

表示 SELECT 的類型,常見的取值,如下表所示:

select_type 含義
SIMPLE 簡單的 SELECT 查詢,查詢中不包含子查詢或者UNION
PRIMARY 查詢中若包含任何複雜的子查詢,最外層查詢标記為該辨別
SUBQUERY 在 SELECT 或 WHERE 清單中包含了子查詢,該子查詢被标記為該辨別
DERIVED 在FROM 清單中包含的子查詢,被标記為 DERIVED(衍生) MYSQL會遞歸執行這些子查詢,把結果放在臨時表中
UNION 若第二個SELECT出現在UNION之後,則标記為UNION ; 若UNION包含在FROM子句的子查詢中,外層SELECT将被标記為 : DERIVED
UNION RESULT 從UNION表擷取結果的SELECT

3.3.4 explain 之 table

展示這一行的資料是關于哪一張表的

3.3.5 explain 之 type

type 顯示的是通路類型,是較為重要的一個名額,可取值為:

type 含義
NULL MySQL不通路任何表,索引,直接傳回結果 ,如 SELECT NOW();
system 表隻有一行記錄(等于系統表),這是 const 類型的特例,一般不會出現
const 表示通過索引一次就找到了,const 用于比較 primary key 或者 unique 索引。因為隻比對一行資料,是以很快。如将主鍵置于 where 清單中,MySQL 就能将該查詢轉換為一個常量。const于将 “主鍵” 或 “唯一” 索引的所有部分與常量值進行比較
eq_ref 類似 ref,差別在于使用的是唯一索引,使用主鍵或唯一索引的關聯查詢(對于多表),關聯查詢出的記錄隻有一條。常見于主鍵或唯一索引掃描
ref 非唯一性索引掃描,即通過非唯一索引進行查詢,傳回比對某個單獨值的所有行。本質上也是一種索引通路,傳回所有比對某個單獨值的所有行(多個)
range 隻檢索給定傳回的行,使用一個索引來選擇行。 where 之後出現 between , < , > , in 等操作。
index index 與 ALL的差別為 index 類型隻是周遊了索引樹, 通常比ALL 快, ALL 是周遊資料檔案。
ALL 将周遊全表以找到比對的行

執行效率:

從上到下依次降低

NULL > system > const > eq_ref > ref > range > index > ALL

一般來說, 我們需要保證查詢至少達到 range 級别, 最好達到ref 。

3.3.6 explain 之 key

  • possible_keys : 顯示可能應用在這張表的索引, 一個或多個
  • key : 實際使用的索引, 如果為NULL, 則沒有使用索引
  • key_len : 表示索引中使用的位元組數, 該值為索引字段最大可能長度,并非實際使用長度,在不損失精确性的前提下, 長度越短越好

3.3.7 explain 之 rows

掃描行的數量。

3.3.8 explain 之 extra

其他的額外的執行計劃資訊,在該列展示 。

extra 含義
using filesort 說明mysql會對資料使用一個非索引字段排序,而不是按照表内的索引順序進行讀取(例如 SELECT * FROM t_user ORDER BY name), 稱為 “檔案排序”(掃描檔案中的内容,然後根據内容排序), 效率低(考慮優化)
using temporary 使用了臨時表儲存中間結果,MySQL在對查詢結果排序時使用臨時表。常見于 order by 和 group by; 效率低(考慮優化)
using index 表示相應的select操作使用了覆寫索引, 避免通路表的資料行, 效率不錯
3.4 show profile 分析SQL

show profiles 能夠在做SQL優化時幫助我們了解時間都耗費到哪裡去了。

  • 檢視目前MySQL是否支援profile(通過 have_profiling 參數)
MySQL進階(2)
  • 檢視profile是否開啟(預設profiling是關閉的,通過 profiling 參數)
MySQL進階(2)
  • 開啟profiling
  • profile功能

通過profile,我們能夠更清楚地了解SQL執行的過程。

首先,我們可以執行一系列的操作,如下圖所示:

SHOW DATABASES;

USE db01;

SHOW TABLES;

SELECT * FROM tb_item WHERE id < 5;

SELECT COUNT(*) FROM tb_item;
           

執行完上述指令之後,再執行show profiles 指令, 來檢視SQL語句執行的耗時:

MySQL進階(2)
  • 對SQL執行過程進行詳細檢視

通過show profile for query query_id 語句可以檢視到該SQL執行過程中每個線程的狀态和消耗的時間:

-- 文法
SHOW PROFILE FOR query Query_ID;
-- 例如
SHOW PROFILE FOR query 5;
           
MySQL進階(2)

【注意】

Sending data 狀态表示MySQL線程開始通路資料行并把結果傳回給用戶端的時間,而不僅僅是傳回給用戶端的時間。由于在Sending data狀态下,MySQL線程往往需要做大量的磁盤讀取操作,是以經常是整各查詢中耗時最長的狀态。

在擷取到最消耗時間的線程狀态後,MySQL支援進一步選擇all、cpu、block io 、context switch、page faults等明細類型類檢視MySQL在使用什麼資源上耗費了過高的時間。

-- 文法
SHOW PROFILE [cpu | all | block io | context switch | page faults] FOR query Query_ID;
           

例如,選擇檢視CPU的耗費時間 :

MySQL進階(2)
字段 含義
Status sql 語句執行的狀态
Duration sql 執行過程中每一個步驟的耗時
CPU_user 目前使用者占有的cpu
CPU_system 系統占有的cpu
3.5 trace分析優化器執行計劃

MySQL5.6提供了對SQL的跟蹤trace, 通過trace檔案能夠進一步了解為什麼優化器選擇A計劃, 而不是選擇B計劃。

打開trace , 設定格式為 JSON,并設定trace最大能夠使用的記憶體大小,避免解析過程中因為預設記憶體過小而不能夠完整展示。

SET optimizer_trace="enabled=on",end_markers_in_json=ON;
SET optimizer_trace_max_mem_size=1000000;
           

執行SQL語句 :

最後, 檢查information_schema.optimizer_trace就可以知道MySQL是如何執行SQL的 :

SELECT * FROM information_schema.optimizer_trace \G; 
-- information_schema 是系統庫,optimizer_trace是其中的一張表
           

4. 索引的操作

索引是資料庫優化最常用也是最重要的手段之一, 通過索引通常可以幫助使用者解決大多數的MySQL的性能優化問題。

4.1 索引的使用

4.1.1 準備環境

CREATE TABLE `tb_seller`(
	`sellerid` VARCHAR (100),
	`name` VARCHAR (100),
	`nickname` VARCHAR (50),
	`password` VARCHAR (60),
	`status` VARCHAR (1),
	`address` VARCHAR (100),
	`createtime` DATETIME,
    PRIMARY KEY(`sellerid`)
)ENGINE=INNODB DEFAULT CHARSET=utf8mb4; 

INSERT INTO `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) VALUES('alibaba','阿裡巴巴','阿裡小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
INSERT INTO `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) VALUES('baidu','百度科技有限公司','百度小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
INSERT INTO `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) VALUES('huawei','華為科技有限公司','華為小店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
INSERT INTO `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) VALUES('itcast','傳智播客教育科技有限公司','傳智播客','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
INSERT INTO `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) VALUES('itheima','黑馬程式員','黑馬程式員','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
INSERT INTO `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) VALUES('luoji','羅技科技有限公司','羅技小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
INSERT INTO `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) VALUES('oppo','OPPO科技有限公司','OPPO官方旗艦店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
INSERT INTO `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) VALUES('ourpalm','掌趣科技股份有限公司','掌趣小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
INSERT INTO `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) VALUES('qiandu','千度科技','千度小店','e10adc3949ba59abbe56e057f20f883e','2','北京市','2088-01-01 12:00:00');
INSERT INTO `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) VALUES('sina','新浪科技有限公司','新浪官方旗艦店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
INSERT INTO `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) VALUES('xiaomi','小米科技','小米官方旗艦店','e10adc3949ba59abbe56e057f20f883e','1','西安市','2088-01-01 12:00:00');
INSERT INTO `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) VALUES('yijia','宜家家居','宜家家居旗艦店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');

-- 建立聯合索引
CREATE INDEX idx_seller_name_sta_addr ON tb_seller(NAME,STATUS,address);
           

4.1.2 避免索引失效

1.全值比對 ,對索引中所有列都指定具體值

該情況下,索引生效,執行效率高。

MySQL進階(2)

2.最左字首法則

如果索引了多列(聯合索引),要遵守最左字首法則。指的是查詢從索引的最左前列開始,并且不跳過索引中的列。

  • 比對最左字首法則,走索引:
MySQL進階(2)
  • 違反最左字首法則(跳過了最左前列索引),索引失效:
MySQL進階(2)
  • 如果符合最左法則,但是出現跳躍某一列(兩頭中間的索引列),隻有最左列索引生效:
MySQL進階(2)

3.範圍查詢右邊的列,不能使用索引

MySQL進階(2)

根據前面的兩個字段name,status 查詢是走索引的,但是最後一個條件address 沒有用到索引。

4.不要在索引列上進行運算操作, 索引将失效

MySQL進階(2)

5.字元串不加單引号,造成索引失效

MySQL進階(2)

由于在查詢時,沒有對字元串加單引号,MySQL的查詢優化器,會自動的進行類型轉換,造成索引失效。

6.盡量使用覆寫索引,避免select ※

盡量使用覆寫索引(隻通路索引的查詢(索引列完全包含查詢列)),即隻查詢建立索引的列,減少select *的使用 。

MySQL進階(2)

如果查詢列,超出索引列,也會降低性能。因為需要回表查詢。

MySQL進階(2)

TIP :

using index :使用覆寫索引的時候就會出現

using where:在查找使用索引的情況下,需要回表去查詢所需的資料

using index condition:查找使用了索引,但是需要回表查詢資料

using index ; using where:查找使用了索引,但是需要的資料都在索引列中能找到,是以不需要回表查詢資料

7.用or分割開的條件, 如果or前的條件中的列有索引,而後面的列中沒有索引,那麼涉及的索引都不會被用到

示例,name字段是索引列 , 而createtime不是索引列,中間是or進行連接配接是不走索引的 :

MySQL進階(2)

NULL表示沒有用到索引(索引失效)。

8.以%開頭的Like模糊查詢,索引失效

如果僅僅是尾部模糊比對,索引不會失效。如果是頭部模糊比對,索引失效。

MySQL進階(2)

解決方案 :

通過覆寫索引來解決

MySQL進階(2)

9.如果MySQL評估使用索引比全表更慢,則不使用索引

比如待查詢的關鍵詞在表中的比例較高,即使給該列建立了索引,也有可能不走索引,因為全表查詢比索引查詢更快。

MySQL進階(2)

10.IS NULL,IS NOT NULL有時索引失效。

當查詢列中大多非空時,IS NOT NULL會失效,因為列中充滿了非空資料,全表掃描比索引查詢快,是以索引失效。

當查詢列中大多為空時,IS NULL會失效,因為列中充滿了空資料,全表掃描比索引查詢快,是以索引失效。

MySQL進階(2)

11.IN走索引,NOT IN索引失效

MySQL進階(2)

12.單列索引和複合索引

盡量使用複合索引,而少使用單列索引 。

建立複合索引

CREATE INDEX idx_name_sta_address ON tb_seller(name, status, address);

就相當于建立了三個索引 : 
	name
	name + status
	name + status + address

           

建立單列索引

CREATE INDEX idx_seller_name ON tb_seller(name);
CREATE INDEX idx_seller_status ON tb_seller(status);
CREATE INDEX idx_seller_address ON tb_seller(address);
           

資料庫會選擇一個最優的索引(辨識度最高索引)來使用,并不會使用全部索引 。

當我們查詢多個列時,顯然利用對應的複合索引進行查詢比單列索引效率更高。

4.2 檢視索引使用情況
SHOW STATUS LIKE 'Handler_read%';	

SHOW GLOBAL STATUS LIKE 'Handler_read%';	
           
MySQL進階(2)
Handler_read_first:索引中第一條被讀的次數。如果較高,表示伺服器正執行大量全索引掃描(這個值越低越好)。
Handler_read_key:如果索引正在工作,這個值代表一個行被索引值讀的次數,如果值越低,表示索引得到的性能改善不高,因為索引不經常使用(這個值越高越好)。
Handler_read_next :按照鍵順序讀下一行的請求數。如果你用範圍限制或如果執行索引掃描來查詢索引列,該值增加。
Handler_read_prev:按照鍵順序讀前一行的請求數。該讀方法主要用于優化ORDER BY … DESC。
Handler_read_rnd :根據固定位置讀一行的請求數。如果你正執行大量查詢并需要對結果進行排序該值較高。你可能使用了大量需要MySQL掃描整個表的查詢或你的連接配接沒有正确使用鍵。這個值較高,意味着運作效率低,應該建立索引來補救。
Handler_read_rnd_next:在資料檔案中讀下一行的請求數。如果你正進行大量的表掃描,該值較高。通常說明你的表索引不正确或寫入的查詢沒有利用索引。

5. SQL優化

5.1 大批量插入資料

環境準備 :

-- 建立表1後再建立一個同結構的表2,代碼不再列舉
CREATE TABLE `tb_user_1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(45) NOT NULL,
  `password` varchar(96) NOT NULL,
  `name` varchar(45) NOT NULL,
  `birthday` datetime DEFAULT NULL,
  `sex` char(1) DEFAULT NULL,
  `email` varchar(45) DEFAULT NULL,
  `phone` varchar(45) DEFAULT NULL,
  `qq` varchar(32) DEFAULT NULL,
  `status` varchar(32) NOT NULL COMMENT '使用者狀态',
  `create_time` datetime NOT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_user_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
           

當使用load指令導入資料的時候,适當的設定可以提高導入的效率。

MySQL進階(2)

對于 InnoDB 類型的表,有以下幾種方式可以提高導入的效率:

1.主鍵順序插入

因為InnoDB類型的表是按照主鍵的順序儲存的,是以将導入的資料按照主鍵的順序排列,可以有效的提高導入資料的效率。如果InnoDB表沒有主鍵,那麼系統會自動預設建立一個内部列作為主鍵,是以如果可以給表建立一個主鍵,将可以利用這點,來提高導入資料的效率。

兩個腳本檔案中均存儲了大量資料。

腳本檔案介紹 :

sql1.log ----> 主鍵有序

sql2.log ----> 主鍵無序

load語句插入資料

-- 從本地檔案中加載資料到某個表,域之間用','分隔,行之間用'\n'分隔
LOAD DATA LOCAL INFILE 檔案路徑 INTO TABLE 表名 FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n';
           

插入ID順序排列資料:

MySQL進階(2)

插入ID無序排列資料:

MySQL進階(2)

2.關閉唯一性校驗

在導入資料前執行 SET UNIQUE_CHECKS=0,關閉唯一性校驗,在導入結束後執行SET UNIQUE_CHECKS=1,恢複唯一性校驗,可以提高導入的效率。

MySQL進階(2)

3.手動送出事務

如果應用使用自動送出的方式,建議在導入前執行 SET AUTOCOMMIT=0,關閉自動送出,導入結束後再執行 SET AUTOCOMMIT=1,打開自動送出,也可以提高導入的效率。

MySQL進階(2)
5.2 優化insert語句

當進行資料的insert操作的時候,可以考慮采用以下幾種優化方案。

1.使用多個值表的insert語句

如果需要同時對一張表插入很多行資料時,應該盡量使用多個值表的insert語句,這種方式将大大的縮減用戶端與資料庫之間的連接配接、關閉等消耗。使得效率比分開執行的單個insert語句快。

原始方式為:

INSERT INTO tb_test VALUES(1,'Tom');
 INSERT INTO tb_test VALUES(2,'Cat');
 INSERT INTO tb_test VALUES(3,'Jerry');
           

優化後的方案為 :

2.在事務中進行資料插入

START TRANSACTION;
 INSERT INTO tb_test VALUES(1,'Tom');
 INSERT INTO tb_test VALUES(2,'Cat');
 INSERT INTO tb_test VALUES(3,'Jerry');
 COMMIT;
           

3.資料有序插入

原始方式為:

INSERT INTO tb_test VALUES(4,'Tim');
INSERT INTO tb_test VALUES(1,'Tom');
INSERT INTO tb_test VALUES(3,'Jerry');
INSERT INTO tb_test VALUES(5,'Rose');
INSERT INTO tb_test VALUES(2,'Cat');
           

優化後

INSERT INTO tb_test VALUES(1,'Tom');
INSERT INTO tb_test VALUES(2,'Cat');
INSERT INTO tb_test VALUES(3,'Jerry');
INSERT INTO tb_test VALUES(4,'Tim');
INSERT INTO tb_test VALUES(5,'Rose');
           
5.3 優化order by語句

5.3.1 環境準備

CREATE TABLE `emp` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(100) NOT NULL,
  `age` INT(3) NOT NULL,
  `salary` INT(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB  DEFAULT CHARSET=utf8mb4;

INSERT INTO `emp` (`id`, `name`, `age`, `salary`) VALUES('1','Tom','25','2300');
INSERT INTO `emp` (`id`, `name`, `age`, `salary`) VALUES('2','Jerry','30','3500');
INSERT INTO `emp` (`id`, `name`, `age`, `salary`) VALUES('3','Luci','25','2800');
INSERT INTO `emp` (`id`, `name`, `age`, `salary`) VALUES('4','Jay','36','3500');
INSERT INTO `emp` (`id`, `name`, `age`, `salary`) VALUES('5','Tom2','21','2200');
INSERT INTO `emp` (`id`, `name`, `age`, `salary`) VALUES('6','Jerry2','31','3300');
INSERT INTO `emp` (`id`, `name`, `age`, `salary`) VALUES('7','Luci2','26','2700');
INSERT INTO `emp` (`id`, `name`, `age`, `salary`) VALUES('8','Jay2','33','3500');
INSERT INTO `emp` (`id`, `name`, `age`, `salary`) VALUES('9','Tom3','23','2400');
INSERT INTO `emp` (`id`, `name`, `age`, `salary`) VALUES('10','Jerry3','32','3100');
INSERT INTO `emp` (`id`, `name`, `age`, `salary`) VALUES('11','Luci3','26','2900');
INSERT INTO `emp` (`id`, `name`, `age`, `salary`) VALUES('12','Jay3','37','4500');

-- 建立複合索引
CREATE INDEX idx_emp_age_salary ON emp(age,salary);
           

5.3.2 兩種排序方式

1.方式一

第一種是通過對傳回資料進行排序,也就是通常說的 filesort 排序,這種情況即為 using filesort,所有不是通過索引直接傳回排序結果的排序都叫 FileSort 排序。

MySQL進階(2)

2.方式二

第二種通過有序索引順序掃描直接傳回有序資料,這種情況即為 using index,不需要額外排序,操作效率高。

MySQL進階(2)

3.多字段排序示例

MySQL進階(2)

4.order by優化思路

了解了MySQL的排序方式,優化目标就清晰了:盡量減少額外的排序,通過索引直接傳回有序資料(查找索引字段的資料),對索引字段的資料進行排序。并且 order by 的順序和索引順序相同, 并且 order by 的字段都是升序,或者都是降序。否則肯定需要額外的操作,這樣就會出現 FileSort。

5.3.3 Filesort 的優化

通過建立合适的索引,能夠減少 filesort 的出現,但是在某些情況下,條件限制不能讓Filesort消失,那就需要加快 filesort的排序操作。對于filesort , MySQL 有兩種排序算法:

1.兩次掃描算法

MySQL4.1 之前,使用該方式排序。首先根據條件取出排序字段和行指針資訊,然後在排序區 sort buffer 中排序,如果sort buffer不夠,則在臨時表 temporary table 中存儲排序結果。完成排序之後,再根據行指針回表讀取記錄,該操作可能會導緻大量随機I/O操作。

2.一次掃描算法

一次性取出滿足條件的所有字段,然後在排序區 sort buffer 中排序後直接輸出結果集。排序時記憶體開銷較大,但是排序效率比兩次掃描算法要高。

MySQL 通過比較系統變量 max_length_for_sort_data 的大小和Query語句取出的字段總大小, 來判定是否那種排序算法,如果max_length_for_sort_data 更大,那麼使用第二種優化之後的算法;否則使用第一種。

可以适當提高 sort_buffer_size 和 max_length_for_sort_data 系統變量,來增大排序區的大小,提高排序的效率。

MySQL進階(2)
5.4 優化group by 語句

由于GROUP BY 實際上也同樣會進行排序操作,而且與ORDER BY 相比,GROUP BY 主要隻是多了排序之後的分組操作。當然,如果在分組的時候還使用了其他的一些聚合函數,那麼還需要一些聚合函數的計算。是以,在GROUP BY 的實作過程中,與 ORDER BY 一樣也可以利用到索引。

1.禁止排序

如果查詢包含 group by 但是使用者想要避免排序結果的消耗, 則可以執行order by null 禁止排序。

優化前:

MySQL進階(2)

優化後

MySQL進階(2)

從上面的例子可以看出,第一個SQL語句需要進行"filesort",而第二個SQL由于order by null 不需要進行 “filesort”, 而上文提過Filesort往往非常耗費時間。

2.建立索引

MySQL進階(2)
5.5 優化嵌套查詢

Mysql4.1版本之後,開始支援SQL的子查詢。這個技術可以使用SELECT語句來建立一個單列的查詢結果,然後把這個結果作為過濾條件用在另一個查詢中。使用子查詢可以一次性的完成很多邏輯上需要多個步驟才能完成的SQL操作,同時也可以避免事務或者表鎖死,并且寫起來也很容易。但是,有些情況下,子查詢是可以被更高效的連接配接(JOIN)替代。

示例 ,查找有角色的所有的使用者資訊 :

執行計劃為 :

MySQL進階(2)

優化後 :

MySQL進階(2)

連接配接(Join)查詢之是以更有效率一些 ,是因為MySQL不需要在記憶體中建立臨時表來完成這個邏輯上需要兩個步驟的查詢工作。

5.6 優化OR條件

對于包含OR的查詢子句,如果要利用索引,則OR之間的每個條件列都必須用到索引 , 而且不能使用到複合索引; 如果沒有索引,則應該考慮增加索引。

擷取 emp 表中的所有的索引 :

MySQL進階(2)

示例 :

explain select * from emp where id = 1 or age = 30;
           
MySQL進階(2)
MySQL進階(2)

建議使用 union 替換 or :

MySQL進階(2)

我們來比較下重要名額,發現主要差别是 type 和 ref 這兩項

type 顯示的是通路類型,是較為重要的一個名額,結果值從好到壞依次是:

system > const > eq_ref > ref > fulltext > ref_or_null >index_merge > unique_subquery > index_subquery > range > index > ALL

UNION 語句的 type 值為 ref,OR 語句的 type 值為 range,可以看到這是一個很明顯的差距

UNION 語句的 ref 值為 const,OR 語句的 type 值為 null,const 表示是常量值引用,非常快

這兩項的差距就說明了 UNION 要優于 OR 。

5.7 優化分頁查詢

一般分頁查詢時,通過建立覆寫索引能夠比較好地提高性能。一個常見又非常頭疼的問題就是 limit 2000000,10 ,此時需要MySQL排序前2000010 記錄,僅僅傳回2000000 - 2000010 的記錄,其他記錄丢棄,查詢排序的代價非常大 。

MySQL進階(2)

5.7.1 優化思路一

在索引上完成排序分頁操作,最後根據主鍵關聯回原表查詢所需要的其他列内容。

MySQL進階(2)

5.7.2 優化思路二

該方案适用于主鍵自增的表,可以把Limit 查詢轉換成某個位置的查詢 。

MySQL進階(2)
5.8 使用SQL提示

SQL提示,是優化資料庫的一個重要手段,簡單來說,就是在SQL語句中加入一些人為的提示來達到優化操作的目的。

5.8.1 USE INDEX

在查詢語句中表名的後面,添加 use index 來提供希望MySQL去參考的索引清單,就可以讓MySQL不再考慮其他可用的索引。

MySQL進階(2)

5.8.2 IGNORE INDEX

如果使用者隻是單純的想讓MySQL忽略一個或者多個索引,則可以使用 ignore index 作為 hint 。

MySQL進階(2)

5.8.3 FORCE INDEX

為強制MySQL使用一個特定的索引,可在查詢中使用 force index 作為hint 。

create index idx_seller_address on tb_seller(address);
           
MySQL進階(2)