一 解決MySql中文亂碼
- 查看字符集
# 查看字符集
show variables like 'character%';
show variables like '%char%';
默認的服務器用了latin1(拉丁,不支持中文),所以會亂碼。
- 修改my-huge.cnf
在/usr/share/mysql/ 中找到my-huge.cnf的配置文件,拷貝其中的my-huge.cnf 到 /etc/ 下, 並命名爲my.cnf
然後修改my.cnf:
[client]
default-character-set=utf8
[mysqld]
character_set_server=utf8
character_set_client=utf8
collation-server=utf8_general_ci
[mysql]
default-character-set=utf8
3. 重新啓動mysql
查看原庫的字符集:show create database mydb
但是原庫的設定不會發生變化,參數修改只對新建的數據庫生效
4. 已生成的庫表字符集如何變更
修改數據庫的字符集
mysql> alter database mydb character set ‘utf8’;
修改數據表的字符集
mysql> alter table mytbl convert to character set ‘utf8’;
但是原有的數據如果是用非’utf8’編碼的話,數據本身不會發生改變。
二 遠程連接linux上的MySql
-
先 ping 一下數據庫服務器的ip 地址確認網絡暢通。
-
關閉數據庫服務的防火牆
service iptables stop -
確認MySql中已經有可以通過遠程登錄的賬戶
select * from mysql.user where user=‘li4’ and host=’%’; -
如果沒有用戶,先執行如下命令:
grant all privileges on*.*
to li4@’%’ identified by ‘123123’;
三 MySQL配置sql_mode
場景
執行下邊的建表語句,並插入數據
# 建表
CREATE TABLE mytbl2 (id INT,NAME VARCHAR(200),age INT,dept INT);
# 插入數據
INSERT INTO mytbl2 VALUES(1,'zhang3',33,101);
INSERT INTO mytbl2 VALUES(2,'li4',34,101);
INSERT INTO mytbl2 VALUES(3,'wang5',34,102);
INSERT INTO mytbl2 VALUES(4,'zhao6',34,102);
INSERT INTO mytbl2 VALUES(5,'tian7',36,102);
查詢每個部門年齡最大的人
SELECT NAME,dept,MAX(age) FROM mytbl2 GROUP BY dept;
結果:
結果顯然不對(年齡與部門正確,名字不對,之所以101部門名字顯示的是zhang3,是因爲它默認取的是部門裏的第一個名字,同理,102部門顯示的名字是wnag5也是不對的),但是沒有報錯(正常情況下,上邊的查詢sql是會報錯的,因爲GROUP BY後沒有NAME字段,所以SELECT後就不可以寫NAME,可以查函數,即MAX(age)可以查),這是因爲沒有配置sql_mode
配置SQL_MODE
sql_mode是個很容易被忽視的變量,默認值是空值,在這種設置下是可以允許一些非法操作的,比如允許一些非法數據的插入。在生產環境必須將這個值設置爲嚴格模式,所以開發、測試環境的數據庫也必須要設置,這樣在開發測試階段就可以發現問題。
查看sql_mode
show variables like 'sql_mode';
可以看到value值默認是空的,執行如下命令設置value值:
set sql_mode='ONLY_FULL_GROUP_BY';
此時,之前不合法的sql就會報錯
# 該sql報錯
SELECT NAME,dept,MAX(age) FROM mytbl2 GROUP BY dept;
value可以有很多取值,各個值代表的含義如下
-
ONLY_FULL_GROUP_BY:
對於GROUP BY聚合操作,如果在SELECT中的列,沒有在GROUP BY中出現,那麼這個SQL是不合法的,因爲列不在GROUP BY從句中 -
NO_AUTO_VALUE_ON_ZERO:
該值影響自增長列的插入。默認設置下,插入0或NULL代表生成下一個自增長值。如果用戶 希望插入的值爲0,而該列又是自增長的,那麼這個選項就有用了。 -
STRICT_TRANS_TABLES:
在該模式下,如果一個值不能插入到一個事務表中,則中斷當前的操作,對非事務表不做限制
NO_ZERO_IN_DATE:
在嚴格模式下,不允許日期和月份爲零 -
NO_ZERO_DATE:
設置該值,mysql數據庫不允許插入零日期,插入零日期會拋出錯誤而不是警告。 -
ERROR_FOR_DIVISION_BY_ZERO:
在INSERT或UPDATE過程中,如果數據被零除,則產生錯誤而非警告。如 果未給出該模式,那麼數據被零除時MySQL返回NULL -
NO_AUTO_CREATE_USER:
禁止GRANT創建密碼爲空的用戶 -
NO_ENGINE_SUBSTITUTION:
如果需要的存儲引擎被禁用或未編譯,那麼拋出錯誤。不設置此值時,用默認的存儲引擎替代,並拋出一個異常 -
PIPES_AS_CONCAT:
將"||"視爲字符串的連接操作符而非或運算符,這和Oracle數據庫是一樣的,也和字符串的拼接函數Concat相類似 -
ANSI_QUOTES:
啓用ANSI_QUOTES後,不能用雙引號來引用字符串,因爲它被解釋爲識別符 -
ORACLE:
設置等同:PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, NO_KEY_OPTIONS, NO_TABLE_OPTIONS, NO_FIELD_OPTIONS, NO_AUTO_CREATE_USER.
四 MySQL邏輯架構
邏輯架構介紹
和其它數據庫相比,MySQL有點與衆不同,它的架構可以在多種不同場景中應用併發揮良好作用。主要體現在存儲引擎的架構上,
插件式的存儲引擎架構將查詢處理和其它的系統任務以及數據的存儲提取相分離。這種架構可以根據業務的需求和實際需要選擇合適的存儲引擎。
-
連接層
最上層是一些客戶端和連接服務,包含本地sock通信和大多數基於客戶端/服務端工具實現的類似於tcp/ip的通信。主要完成一些類似於連接處理、授權認證、及相關的安全方案。在該層上引入了線程池的概念,爲通過認證安全接入的客戶端提供線程。同樣在該層上可以實現基於SSL的安全鏈接。服務器也會爲安全接入的每個客戶端驗證它所具有的操作權限。 -
服務層
2.1 Management Serveices & Utilities: 系統管理和控制工具
2.2 SQL Interface: SQL接口
接受用戶的SQL命令,並且返回用戶需要查詢的結果。比如select from就是調用SQL Interface
2.3 Parser: 解析器
SQL命令傳遞到解析器的時候會被解析器驗證和解析。
2.4 Optimizer: 查詢優化器。
SQL語句在查詢之前會使用查詢優化器對查詢進行優化。
用一個例子就可以理解: select uid,name from user where gender= 1;
優化器來決定先投影還是先過濾。
2.5 Cache和Buffer: 查詢緩存。
如果查詢緩存有命中的查詢結果,查詢語句就可以直接去查詢緩存中取數據。
這個緩存機制是由一系列小緩存組成的。比如表緩存,記錄緩存,key緩存,權限緩存等 -
引擎層
存儲引擎層,存儲引擎真正的負責了MySQL中數據的存儲和提取,服務器通過API與存儲引擎進行通信。不同的存儲引擎具有的功能不同,這樣我們可以根據自己的實際需要進行選取。後面介紹MyISAM和InnoDB -
存儲層
數據存儲層,主要是將數據存儲在運行於裸設備的文件系統之上,並完成與存儲引擎的交互。
利用show profile 查看sql的執行週期
查看週期是否開啓
show variables like '%profiling%';
說明已開啓週期,如果沒有開啓的話,執行下邊的命令進行開啓
set profiling=1;
開啓後,我們隨便做幾次查詢sql,然後通過以下命令顯示最近的幾次查詢
# 顯示最近的幾次查詢
show profiles;
查看某一條sql的執行步驟
# 最後的Query_ID的值就是上邊的第一列的值
# show profile cpu,block io for query Query_ID
show profile cpu,block io for query 3
下邊的結果就是某一條sql的生命週期
第一次查詢時,生命週期很長(如上圖),但是再次執行相同的查詢sql時,就不會有那麼長的生命週期了,第二次查詢的週期如下:
查詢流程圖
-
首先,mysql的查詢流程大致是:
mysql客戶端通過協議與mysql服務器建連接,發送查詢語句,先檢查查詢緩存,如果命中,直接返回結果,否則進行語句解析,也就是說,在解析查詢之前,服務器會先訪問查詢緩存(query cache)——它存儲SELECT語句以及相應的查詢結果集。如果某個查詢結果已經位於緩存中,服務器就不會再對查詢進行解析、優化、以及執行。它僅僅將緩存中的結果返回給用戶即可,這將大大提高系統的性能。 -
語法解析器和預處理:首先mysql通過關鍵字將SQL語句進行解析,並生成一顆對應的“解析樹”。mysql解析器將使用mysql語法規則驗證和解析查詢;預處理器則根據一些mysql規則進一步檢查解析樹是否合法。
-
查詢優化器當解析樹被認爲是合法的了,並且由優化器將其轉化成執行計劃。一條查詢可以有很多種執行方式,最後都返回相同的結果。優化器的作用就是找到這其中最好的執行計劃。。
-
然後,mysql默認使用的BTREE索引,並且一個大致方向是:無論怎麼折騰sql,至少在目前來說,mysql最多隻用到表中的一個索引。
SQL執行順序
我們寫sql的順序
隨着Mysql版本的更新換代,其優化器也在不斷的升級,優化器會分析不同執行順序產生的性能消耗不同而動態調整執行順序。
下面是經常出現的查詢順序(MySql內部執行的順序):
五 MySQL存儲引擎
查看當前MySql引擎信息
查看mysql現在已提供什麼存儲引擎
show engines;
看你的mysql當前默認的存儲引擎
show variables like '%storage_engine%'
各個引擎簡介
-
InnoDB存儲引擎
InnoDB是MySQL的默認事務型引擎,它被設計用來處理大量的短期(short-lived)事務。除非有非常特別的原因需要使用其他的存儲引擎,否則應該優先考慮InnoDB引擎。 -
MyISAM存儲引擎
MyISAM提供了大量的特性,包括全文索引、壓縮、空間函數(GIS)等,但MyISAM不支持事務和行級鎖,有一個毫無疑問的缺陷就是崩潰後無法安全恢復。 -
Archive引擎
Archive檔案存儲引擎只支持INSERT和SELECT操作,在MySQL5.1之前不支持索引。
Archive表適合日誌和數據採集類應用。
根據英文的測試結論來看,Archive表比MyISAM表要小大約75%,比支持事務處理的InnoDB表小大約83%。 -
Blackhole引擎
Blackhole引擎沒有實現任何存儲機制,它會丟棄所有插入的數據,不做任何保存。但服務器會記錄Blackhole表的日誌,所以可以用於複製數據到備庫,或者簡單地記錄到日誌。但這種應用方式會碰到很多問題,因此並不推薦。 -
CSV引擎
CSV引擎可以將普通的CSV文件作爲MySQL的表來處理,但不支持索引。
CSV引擎可以作爲一種數據交換的機制,非常有用。
CSV存儲的數據直接可以在操作系統裏,用文本編輯器,或者excel讀取。 -
Memory引擎
如果需要快速地訪問數據,並且這些數據不會被修改,重啓以後丟失也沒有關係,那麼使用Memory表是非常有用。Memory表至少比MyISAM表要快一個數量級。 -
Federated引擎
Federated引擎是訪問其他MySQL服務器的一個代理,儘管該引擎看起來提供了一種很好的跨服務器的靈活性,但也經常帶來問題,因此默認是禁用的。
MyISAM引擎和InnoDB引擎
對比項 | MyISAM | InnoDB |
---|---|---|
外鍵 | 不支持 | 支持 |
事務 | 不支持 | 支持 |
行表鎖 | 表鎖,即使操作一條記錄也會鎖住整個表,不適合高併發的操作 | 行鎖,操作時只鎖某一行,不對其它行有影響, 適合高併發的操作 |
緩存 | 只緩存索引,不緩存真實數據 | 不僅緩存索引還要緩存真實數據,對內存要求較高,而且內存大小對性能有決定性的影響 |
關注點 | 節省資源、消耗少、簡單業務 | 併發寫、事務、更大資源 |
默認安裝 | Y | Y |
默認使用 | N | Y |
自帶系統表使用 | Y | N |
六 常見通用的Join查詢
建表
CREATE TABLE `t_dept` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`deptName` VARCHAR(30) DEFAULT NULL,
`address` VARCHAR(40) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `t_emp` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(3) DEFAULT NULL,
`deptId` INT(11) DEFAULT NULL,
empno int not null,
PRIMARY KEY (`id`),
KEY `idx_dept_id` (`deptId`)
#CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
插入數據
INSERT INTO t_dept(deptName,address) VALUES('華山','華山');
INSERT INTO t_dept(deptName,address) VALUES('丐幫','洛陽');
INSERT INTO t_dept(deptName,address) VALUES('峨眉','峨眉山');
INSERT INTO t_dept(deptName,address) VALUES('武當','武當山');
INSERT INTO t_dept(deptName,address) VALUES('明教','光明頂');
INSERT INTO t_dept(deptName,address) VALUES('少林','少林寺');
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('風清揚',90,1,100001);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('嶽不羣',50,1,100002);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('令狐沖',24,1,100003);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('洪七公',70,2,100004);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('喬峯',35,2,100005);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('滅絕師太',70,3,100006);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('周芷若',20,3,100007);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('張三丰',100,4,100008);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('張無忌',25,5,100009);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('韋小寶',18,null,100010);
七種JOIN
- 所有有門派的人員信息 ( A、B兩表共有)
select * from t_emp a inner join t_dept b on a.deptId = b.id;
- 列出所有用戶,並顯示其機構信息 (A的全集)
select * from t_emp a left join t_dept b on a.deptId = b.id;
- 列出所有門派 (B的全集)
select * from t_dept a left join t_emp b on a.id = b.deptId;
- 所有不入門派的人員 (A的獨有)
select * from t_emp a left join t_dept b on a.deptId = b.id where b.id is null;
- 所有沒人入的門派 (B的獨有)
select * from t_dept b left join t_emp a on a.deptId = b.id where a.deptId is null;
- 列出所有人員和機構的對照關係(AB全有)
#MySQL Full Join的實現 因爲MySQL不支持FULL JOIN,下面是替代方法
#left join + union(可去除重複數據)+ right join
SELECT A.*,B.* FROM t_emp A LEFT JOIN t_dept B ON A.deptId = B.id
UNION
SELECT A.*,B.* FROM t_emp A RIGHT JOIN t_dept B ON A.deptId = B.id where A.id is null;
- 列出所有沒入派的人員和沒人入的門派(A的獨有+B的獨有)
SELECT A.*,B.* FROM t_emp A LEFT JOIN t_dept B ON A.deptId = B.id WHERE B.`id` IS NULL
UNION
SELECT A.*,B.* FROM t_emp A RIGHT JOIN t_dept B ON A.deptId = B.id WHERE A.`id` IS NULL;
七 索引
7.1 索引介紹
MySQL官方對索引的定義爲:索引(Index)是幫助MySQL高效獲取數據的數據結構。可以得到索引的本質:索引是數據結構
索引的目的在於提高查詢效率,可以類比字典,
如果要查“mysql”這個單詞,我們肯定需要定位到m字母,然後從上往下找到y字母,再找到剩下的sql。
如果沒有索引,那麼你可能需要a----z,如果我想找到Java開頭的單詞呢?或者Oracle開頭的單詞呢?是不是覺得如果沒有索引,這個事情根本無法完成?
索引—數據結構
可以將索引簡單理解爲排好序的快速查找的數據結構;
在數據之外,數據庫系統還維護着滿足特定查找算法的數據結構,這些數據結構以某種方式引用(指向)數據,這樣就可以在這些數據結構上實現高級查找算法。這種數據結構,就是索引。下圖就是一種可能的索引方式示例:
左邊是數據表,一共有兩列七條記錄,最左邊的是數據記錄的物理地址;爲了加快Col2的查找,可以維護一個右邊所示的二叉查找樹,每個節點分別包含索引鍵值和一個指向對應數據記錄物理地址的指針,這樣就可以運用二叉查找在一定的複雜度內獲取到相應數據,從而快速的檢索出符合條件的記錄。
結論
數據本身之外,數據庫還維護着一個滿足特定查找算法的數據結構,這些數據結構以某種方式指向數據,這樣就可以在這些數據結構的基礎上實現高級查找算法,這種數據結構就是索引。
一般來說索引本身也很大,不可能全部存儲在內存中,因此索引往往以索引文件的形式存儲在磁盤上
索引優勢
- 類似大學圖書館建書目索引,提高數據檢索的效率,降低數據庫的IO成本
- 通過索引列對數據進行排序,降低數據排序的成本,降低了CPU的消耗
索引劣勢
- 雖然索引大大提高了查詢速度,同時卻會降低更新表的速度,如對錶進行INSERT、UPDATE和DELETE。因爲更新表時,MySQL不僅要保存數據,還要保存一下索引文件每次更新添加了索引列的字段,會調整因爲更新所帶來的鍵值變化後的索引信息;
- 實際上索引也是一張表,該表保存了主鍵與索引字段,並指向實體表的記錄,所以索引列也是要佔用空間的
7.2 mysql索引結構
7.2.1 BTree索引
原理圖
【初始化介紹】
一顆b樹,淺藍色的塊我們稱之爲一個磁盤塊,可以看到每個磁盤塊包含幾個數據項(深藍色所示)和指針(黃色所示),
如磁盤塊1包含數據項17和35,包含指針P1、P2、P3,P1表示小於17的磁盤塊,P2表示在17和35之間的磁盤塊,P3表示大於35的磁盤塊。
真實的數據存在於葉子節點即3、5、9、10、13、15、28、29、36、60、75、79、90、99。
非葉子節點不存儲真實的數據,只存儲指引搜索方向的數據項,如17、35並不真實存在於數據表中。
【查找過程】
如果要查找數據項29,那麼首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找確定29在17和35之間,鎖定磁盤塊1的P2指針,內存時間因爲非常短(相比磁盤的IO)可以忽略不計,通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內存,發生第二次IO,29在26和30之間,鎖定磁盤塊3的P2指針,通過指針加載磁盤塊8到內存,發生第三次IO,同時內存中做二分查找找到29,結束查詢,總計三次IO。
真實的情況是,3層的b+樹可以表示上百萬的數據,如果上百萬的數據查找只需要三次IO,性能提高將是巨大的,如果沒有索引,每個數據項都要發生一次IO,那麼總共需要百萬次的IO,顯然成本非常非常高。
時間複雜度
同一問題可用不同算法解決,而一個算法的質量優劣將影響到算法乃至程序的效率。算法分析的目的在於選擇合適算法和改進算法。
7.2.2 B+Tree索引索引
原理圖
B樹和B+樹的區別
- B-樹的關鍵字和記錄是放在一起的,葉子節點可以看作外部節點,不包含任何信息;B+樹的非葉子節點中只有關鍵字和指向下一個節點的索引,記錄只放在葉子節點中。
- 在B-樹中,越靠近根節點的記錄查找時間越快,只要找到關鍵字即可確定記錄的存在;而B+樹中每個記錄的查找時間基本是一樣的,都需要從根節點走到葉子節點,而且在葉子節點中還要再比較關鍵字。從這個角度看B-樹的性能好像要比B+樹好,而在實際應用中卻是B+樹的性能要好些。因爲B+樹的非葉子節點不存放實際的數據,這樣每個節點可容納的元素個數比B-樹多,樹高比B-樹小,這樣帶來的好處是減少磁盤訪問次數。儘管B+樹找到一個記錄所需的比較次數要比B-樹多,但是一次磁盤訪問的時間相當於成百上千次內存比較的時間,因此實際中B+樹的性能可能還會好些,而且B+樹的葉子節點使用指針連接在一起,方便順序遍歷(例如查看一個目錄下的所有文件,一個表中的所有記錄等),這也是很多數據庫和文件系統使用B+樹的緣故。
思考:爲什麼說B+樹比B-樹更適合實際應用中操作系統的文件索引和數據庫索引?
- B+樹的磁盤讀寫代價更低
B+樹的內部結點並沒有指向關鍵字具體信息的指針。因此其內部結點相對B 樹更小。如果把所有同一內部結點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀入內存中的需要查找的關鍵字也就越多。相對來說IO讀寫次數也就降低了。 - B+樹的查詢效率更加穩定
由於非終結點並不是最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查找必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當。
7.2.3 聚簇索引與非聚簇索引
聚簇索引並不是一種單獨的索引類型,而是一種數據存儲方式。術語‘聚簇’表示數據行和相鄰的鍵值聚簇的存儲在一起。
如下圖,左側的索引就是聚簇索引,因爲數據行在磁盤的排列和索引排序保持一致。
7.3 MySQL索引分類
7.3.1 單值索引
即一個索引只包含單個列,一個表可以有多個單列索引
語法
隨表一起建索引: KEY (customer_name)
CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200),
PRIMARY KEY(id),
KEY (customer_name)
);
單獨建單值索引:
CREATE INDEX idx_customer_name ON customer(customer_name);
刪除索引:
DROP INDEX idx_customer_name on customer;
7.3.2 唯一索引
索引列的值必須唯一,但允許有空值
語法
隨表一起建索引: UNIQUE (customer_no)
CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200),
PRIMARY KEY(id),
KEY (customer_name),
UNIQUE (customer_no)
);
單獨建唯一索引:
CREATE UNIQUE INDEX idx_customer_no ON customer(customer_no);
刪除索引:
DROP INDEX idx_customer_no on customer ;
7.3.3 主鍵索引
設定爲主鍵後數據庫會自動建立索引,innodb爲聚簇索引
語法
隨表一起建索引:PRIMARY KEY(id)
CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200),
PRIMARY KEY(id)
);
CREATE TABLE customer2 (id INT(10) UNSIGNED ,customer_no VARCHAR(200),customer_name VARCHAR(200),
PRIMARY KEY(id)
);
單獨建主鍵索引:
ALTER TABLE customer add PRIMARY KEY customer(customer_no);
刪除建主鍵索引:
ALTER TABLE customer drop PRIMARY KEY ;
修改建主鍵索引:
必須先刪除掉(drop)原索引,再新建(add)索引
7.3.4 複合索引
即一個索引包含多個列
語法
隨表一起建索引: KEY (customer_no,customer_name)
CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200),
PRIMARY KEY(id),
KEY (customer_name),
UNIQUE (customer_name),
KEY (customer_no,customer_name)
);
單獨建索引:
CREATE INDEX idx_no_name ON customer(customer_no,customer_name);
刪除索引:
DROP INDEX idx_no_name on customer ;
7.3.5 基本語法
創建
CREATE [UNIQUE ] INDEX [indexName] ON table_name(column))
刪除
DROP INDEX [indexName] ON mytable;
查看
SHOW INDEX FROM table_name\G
四種方式添加索引
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list): 該語句添加一個主鍵,這意味着索引值必須是唯一的,且不能爲NULL。
ALTER TABLE tbl_name ADD UNIQUE index_name (column_list): 這條語句創建索引的值必須是唯一的(除了NULL外,NULL可能會出現多次)。
ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引,索引值可出現多次。
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):該語句指定了索引爲 FULLTEXT ,用於全文索引。
7.4 哪些情況需要創建索引
-
主鍵自動建立唯一索引
-
頻繁作爲查詢條件的字段應該創建索引
-
查詢中與其它表關聯的字段,外鍵關係建立索引
-
單鍵/組合索引的選擇問題, 組合索引性價比更高
-
查詢中排序的字段,排序字段若通過索引去訪問將大大提高排序速度
-
查詢中統計或者分組字段
7.5 哪些情況不要創建索引
- 表記錄太少
- 經常增刪改的表或者字段:創建索引的話,雖然提高了查詢速度,但同時卻會降低更新表的速度,如對錶進行INSERT、UPDATE和DELETE。因爲更新表時,MySQL不僅要保存數據,還要保存一下索引文件
- Where條件裏用不到的字段不創建索引
- 過濾性不好的不適合建索引
八 性能分析—Explain
8.1 Explain介紹
使用EXPLAIN關鍵字可以模擬優化器執行SQL查詢語句,從而知道MySQL是
如何處理你的SQL語句的,分析你的查詢語句或是表結構的性能瓶頸,官網介紹
8.2 Explain作用
- 找出表的讀取順序
- 查看哪些索引可以使用
- 數據讀取操作的操作類型
- 查看哪些索引被實際使用
- 查看錶之間的引用
- 每張表有多少行被物理查詢
Explain包含的信息
創建腳本
CREATE TABLE t1(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t2(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t3(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t4(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
INSERT INTO t1(content) VALUES(CONCAT('t1_',FLOOR(1+RAND()*1000)));
INSERT INTO t2(content) VALUES(CONCAT('t2_',FLOOR(1+RAND()*1000)));
INSERT INTO t3(content) VALUES(CONCAT('t3_',FLOOR(1+RAND()*1000)));
INSERT INTO t4(content) VALUES(CONCAT('t4_',FLOOR(1+RAND()*1000)));
使用方式
Explain+SQl查詢語句即可,如下
8.3 各個字段含義
id
select查詢的序列號,包含一組數字,表示查詢中執行select子句或操作表的順序;
三種情況:
- id相同,執行順序由上至下:
id相同,執行順序由上至下 - lid不同,如果是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行:
d不同,如果是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行 - id相同不同,同時存在:
id如果相同,可以認爲是一組,從上往下順序執行;
在所有組中,id值越大,優先級越高,越先執行
衍生 = DERIVED
關注點:
id號每個號碼,表示一趟獨立的查詢。一個sql 的查詢趟數越少越好;
select_type
取值範圍以及不同取值代表的含義
- SIMPLE:簡單的 select 查詢,查詢中不包含子查詢或者UNION
- PRIMARY:查詢中若包含任何複雜的子部分,最外層查詢則被標記爲Primary
- DERIVED:在FROM列表中包含的子查詢被標記爲DERIVED(衍生)MySQL會遞歸執行這些子查詢, 把結果放在臨時表裏。
- SUBQUERY:在SELECT或WHERE列表中包含了子查詢
- DEPENDENT SUBQUERY:在SELECT或WHERE列表中包含了子查詢,子查詢基於外層
- UNCACHEABLE SUBQUREY:無緩存查詢
SHOW VARIABLES LIKE '%lower_case_table_names%';
SELECT @@lower_case_table_names FROM DUAL;
- UNION: 若第二個SELECT出現在UNION之後,則被標記爲UNION;若UNION包含在FROM子句的子查詢中,外層SELECT將被標記爲:DERIVED
- UNION RESULT:從UNION表獲取結果的SELECT
table
顯示這一行的數據是關於哪張表的
type
顯示查詢使用了何種類型,從最好到最差依次是:
system>const>eq_ref>ref>range>index>ALL
type顯示的是訪問類型,是較爲重要的一個指標,結果值從最好到最壞依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
-
system
表只有一行記錄(等於系統表),這是const類型的特列,平時不會出現,這個也可以忽略不計 -
const
表示通過索引一次就找到了,const用於比較primary key或者unique索引。因爲只匹配一行數據,所以很快;如將主鍵置於where列表中,MySQL就能將該查詢轉換爲一個常量 -
eq_ref
唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配。常見於主鍵或唯一索引掃描 -
ref
非唯一性索引掃描,返回匹配某個單獨值的所有行.
本質上也是一種索引訪問,它返回所有匹配某個單獨值的行,然而,它可能會找到多個符合條件的行,所以他應該屬於查找和掃描的混合體 -
range
只檢索給定範圍的行,使用一個索引來選擇行。key 列顯示使用了哪個索引;一般就是在你的where語句中出現了between、<、>、in等的查詢;這種範圍掃描索引掃描比全表掃描要好,因爲它只需要開始於索引的某一點,而結束語另一點,不用掃描全部索引; -
index
出現index是sql使用了索引但是沒用通過索引進行過濾,一般是使用了覆蓋索引或者是利用索引進行了排序分組; -
all
Full Table Scan,將遍歷全表以找到匹配的行 -
index_merge
在查詢過程中需要多個索引組合使用,通常出現在有 or 的關鍵字的sql中 -
ref_or_null
對於某個字段既需要關聯條件,也需要null值得情況下。查詢優化器會選擇用ref_or_null連接查詢 -
index_subquery
利用索引來關聯子查詢,不再全表掃描。 -
unique_subquery
該聯接類型類似於index_subquery。 子查詢中的唯一索引;
備註:一般來說,得保證查詢至少達到range級別,最好能達到ref。
possible_keys
顯示可能應用在這張表中的索引,一個或多個;查詢涉及到的字段上若存在索引,則該索引將被列出,但不一定被查詢實際使用
key
實際使用的索引。如果爲NULL,則沒有使用索引;
查詢中若使用了覆蓋索引,則該索引和查詢的select字段重疊
key_len
表示索引中使用的字節數,可通過該列計算查詢中使用的索引的長度。
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.name LIKE ‘ab%’;
如何計算
1 、先看索引上字段的類型+長度比,如 int=4 ; varchar(20) =20 ; char(20) =20
2 、如果是varchar或者char這種字符串字段,視字符集要乘不同的值,比如utf-8 要乘 3,GBK要乘2
3 、varchar這種動態字符串要加2個字節
4、 允許爲空的字段要加1個字節
第一組
key_len=age的字節長度+name的字節長度=4+1 + ( 20*3+2)=5+62=67
第二組
key_len=age的字節長度=4+1=5
key_len字段能夠幫你檢查是否充分的利用上了索引
ref
顯示索引的哪一列被使用了,如果可能的話,是一個常數。哪些列或常量被用於查找索引列上的值
rows
rows列顯示MySQL認爲它執行查詢時必須檢查的行數。
越少越好
Extra
包含不適合在其他列中顯示但十分重要的額外信息(下邊標紅的情況下,就需要優化sql了)
-
Using filesort
出現filesort的情況:
優化後(創建索引),不再出現filesort的情況:
查詢中排序的字段,排序字段若通過索引去訪問將大大提高排序速度;
說明mysql會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取。
MySQL中無法利用索引完成的排序操作稱爲“文件排序” -
Using temporary
優化前存在 using temporary 和 using filesort
優化前存在 using temporary 和 using filesort 不在,性能發生明顯變化(優化:創建索引):
使了用臨時表保存中間結果,MySQL在對查詢結果排序時使用臨時表。常見於排序 order by 和分組查詢 group by。 -
USING index
表示相應的select操作中使用了覆蓋索引(Covering Index),避免訪問了表的數據行,效率不錯!
如果同時出現using where,表明索引被用來執行索引鍵值的查找;
如果沒有同時出現using where,表明索引只是用來讀取數據而非利用索引執行查找。
利用索引進行了排序或分組 -
Using where
表明使用了where過濾 -
using join buffer
使用了連接緩存 -
impossible where
where子句的值總是false,不能用來獲取任何元組 -
select tables optimized away
在沒有GROUPBY子句的情況下,基於索引優化MIN/MAX操作或者
對於MyISAM存儲引擎優化COUNT(*)操作,不必等到執行階段再進行計算,
查詢執行計劃生成的階段即完成優化。
8.4 通過案例實現查詢優化
8.4.1 批量插入數據
建表
CREATE TABLE `dept` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`deptName` VARCHAR(30) DEFAULT NULL,
`address` VARCHAR(40) DEFAULT NULL,
ceo INT NULL ,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `emp` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`empno` INT NOT NULL ,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(3) DEFAULT NULL,
`deptId` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`)
#CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
設置參數
創建批量插入函數之前,需要設置參數,防止創建函數時報錯;
創建函數時,假如報錯:This function has none of DETERMINISTIC…
由於開啓過慢查詢日誌(比較耗性能);因爲我們開啓了 bin-log, 我們就必須爲我們的function指定一個參數。
show variables like ‘log_bin_trust_function_creators’;
執行如下sql(允許創建函數)
set global log_bin_trust_function_creators=1;
這樣添加了參數以後,如果mysqld重啓,上述參數又會消失,永久方法:
windows下my.ini[mysqld]加上:log_bin_trust_function_creators=1
linux下 /etc/my.cnf下my.cnf[mysqld]加上:log_bin_trust_function_creators=1
創建函數
- 隨機產生字符串的函數,函數名:rand_string
DELIMITER $$
# n:要生成的隨機字符串的長度,調這個函數時傳具體的值即可
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i + 1;
END WHILE;
RETURN return_str;
END $$
#假如要刪除
#drop function rand_string;
- 隨機產生部門編號的函數
#用於隨機產生多少到多少的編號
DELIMITER $$
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num -from_num+1)) ;
RETURN i;
END$$
#假如要刪除
#drop function rand_num;
創建存儲過程
- 創建往emp表中插入數據的存儲過程
DELIMITER $$
CREATE PROCEDURE insert_emp( START INT , max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
#set autocommit =0 把autocommit設置成0
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO emp (empno, NAME ,age ,deptid ) VALUES ((START+i) ,rand_string(6) ,
# 年齡:30-50,部門編號:1-10000
rand_num(30,50),rand_num(1,10000));
UNTIL i = max_num
END REPEAT;
COMMIT;
END$$
#刪除
# DELIMITER ;
# drop PROCEDURE insert_emp;
- 創建往dept表中插入數據的存儲過程
#執行存儲過程,往dept表添加隨機數據
DELIMITER $$
CREATE PROCEDURE `insert_dept`( max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO dept ( deptname,address,ceo ) VALUES (rand_string(8),rand_string(10),rand_num(1,500000));
UNTIL i = max_num
END REPEAT;
COMMIT;
END$$
#刪除
# DELIMITER ;
# drop PROCEDURE insert_dept;
調用存儲過程,批量插入數據
- dept
#執行存儲過程,往dept表添加1萬條數據
DELIMITER ;
CALL insert_dept(10000);
- emp
#執行存儲過程,往emp表添加50萬條數據
DELIMITER ;
CALL insert_emp(100000,500000);
8.4.2 批量刪除某個表上的所有索引
爲了演示後期優化效果,先把非主鍵的索引都刪掉
存儲過程
刪除的是非主鍵的索引
DELIMITER $$
CREATE PROCEDURE `proc_drop_index`(dbname VARCHAR(200),tablename VARCHAR(200))
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE ct INT DEFAULT 0;
DECLARE _index VARCHAR(200) DEFAULT '';
DECLARE _cur CURSOR FOR SELECT index_name FROM information_schema.STATISTICS WHERE table_schema=dbname AND table_name=tablename AND seq_in_index=1 AND index_name <>'PRIMARY' ;
DECLARE CONTINUE HANDLER FOR NOT FOUND set done=2 ;
OPEN _cur;
FETCH _cur INTO _index;
WHILE _index<>'' DO
SET @str = CONCAT("drop index ",_index," on ",tablename );
PREPARE sql_str FROM @str ;
EXECUTE sql_str;
DEALLOCATE PREPARE sql_str;
SET _index='';
FETCH _cur INTO _index;
END WHILE;
CLOSE _cur;
END$$
執行存儲過程
# 兩個參數分別是數據庫名、表名
CALL proc_drop_index("dbname","tablename");
8.4.3 單表使用索引及常見索引失效的情況
案例(索引失效)
- 全值匹配最好
# 不加EXPLAIN ,且沒有創建索引時,耗時大概0.016秒
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30
說明需要優化(創建索引)
創建索引後
CREATE INDEX idx_age ON emp(age);
再次執行:
# 不加EXPLAIN ,且沒有創建索引時,耗時大概0.008秒
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30
結果
注意
查詢語句where後有多個字段作爲查詢條件:
SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4 AND emp.name = 'abcd'
此時,建立單值索引也會有效果,但是沒有建立聯合索引更加有效
# 建立單值索引
CREATE INDEX idx_age ON emp(age);
# 建立聯合索引
CREATE INDEX idx_age_deptid_name ON emp(age,deptid,NAME)
即存在多個字段作爲查詢條件時,創建聯合索引是最好的;
- 最佳左前綴法則
如果系統經常出現的sql如下:
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.name = 'abcd'
或者
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.deptid=1 AND emp.name = 'abcd'
create index idx_age_deptid_name on emp(age,deptId,NAME);
那原來的索引idx_age_deptid_name 還能否正常使用?
如果索引了多列,要遵守最左前綴法則。指的是查詢從索引的最左前列開始,並且不跳過索引中的列(即中間不可以間斷);
場景1:
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.name = 'abcd'
可以看到,只用到了age索引(int類型的key_len是5),沒用到name索引,因爲中間把dept字段跳過了(間斷了),間斷後,name字段的索引也用不上了;雖然可以正常使用,但是隻有部分被使用到了。;
場景2:
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.deptid=20 AND emp.name = 'abd'
完全沒有使用上索引
結論:
過濾條件要使用索引必須按照索引建立時的順序,依次滿足,一旦跳過某個字段,索引後面的字段都無法被使用。
- 不要在索引列上做任何操作(計算、函數、(自動or手動)類型轉換),那樣會導致索引失效而轉向全表掃描
這兩條sql哪種寫法更好
# 情況1
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name LIKE 'abc%'
# 情況2
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE LEFT(emp.name,3) = 'abc'
第一種
第二種
結論:由於在name(索引列)字段上做了函數操作(left函數),所以會使得索引失效
- 存儲引擎不能使用索引中"範圍條件"右邊的列
建立索引:
CREATE INDEX idx_age_deptid_name ON emp(age,deptid,NAME)
下邊的NAME字段索引會失效
# age、deptId、NAME建了一個複合索引,deptId條件是一個範圍,則deptId右邊的name字段的索引就會失效
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.deptId>20 AND emp.name = 'abc' ;
建立索引的語句改爲(將name與deptid順序換了):
CREATE INDEX idx_age_name_deptid ON emp(age,Name,deptid)
此時查詢語句就會把age、name、deptid的索引都使用上
# age、name、deptid三字段的索引都會有效
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.deptId>20 AND emp.name = 'abc' ;
結論:此處索引是否有效,與查詢sql裏的字段順序無關,與建複合索引裏字段的順序有關
- mysql 在使用不等於(!= 或者<>)的時候無法使用索引會導致全表掃描
CREATE INDEX idx_name ON emp(NAME);
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name <> 'abc' ;
- is not null 也無法使用索引,但是is null是可以使用索引的
UPDATE emp SET age =NULL WHERE id=123456;
下列哪個sql語句可以用到索引
# 使用了索引
EXPLAIN SELECT * FROM emp WHERE age IS NULL;
# 沒有使用索引
EXPLAIN SELECT * FROM emp WHERE age IS NOT NULL;
-
like以通配符開頭(’%abc…’)mysql索引失效會變成全表掃描的操作
-
字符串不加單引號索引失效
總結
假設聯合索引:index(a,b,c)
Where語句 | 索引是否被使用 |
---|---|
where a = 3 | Y,使用到a |
where a = 3 and b = 5 | Y,使用到a,b |
where a = 3 and b = 5 and c = 4 | Y,使用到a,b,c |
where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 | N |
where a = 3 and c = 5 | 使用到a, 但是c不可以,b中間斷了 |
where a = 3 and b > 4 and c = 5 | 使用到a和b, c不能用在範圍之後,b斷了 |
where a is null and b is not null | is null 支持索引 但是is not null 不支持,所以 a 可以使用索引,但是 b不可以使用 |
where a <> 3 | 不能使用索引 |
where abs(a) =3 | 不能使用 索引 |
where a = 3 and b like 'kk%' and c = 4 | Y,使用到a,b,c |
where a = 3 and b like '%kk' and c = 4 | Y,只用到a |
where a = 3 and b like '%kk%' and c = 4 | Y,只用到a |
where a = 3 and b like 'k%kk%' and c = 4 | Y,使用到a,b,c |
一般性建議
- 對於單鍵索引,儘量選擇針對當前query過濾性更好的索引。
- 在選擇組合索引的時候,當前Query中過濾性最好的字段在索引字段順序中,位置越靠前越好。
- 在選擇組合索引的時候,儘量選擇可以能夠包含當前query中的where字句中更多字段的索引。
- 在選擇組合索引的時候,如果某個字段可能出現範圍查詢時,儘量把這個字段放在索引次序的最後面。
- 書寫sql語句時,儘量避免造成索引失效的情況。
8.4.4 關聯查詢優化
建表SQL
CREATE TABLE IF NOT EXISTS `class` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `book` (
`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`bookid`)
);
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
案例1
# 下面開始explain分析
EXPLAIN SELECT * FROM book LEFT JOIN class ON class.card = book.card;
結論:type 有All
創建索引
ALTER TABLE `book` ADD INDEX Y ( `card`);
再次執行查詢語句
# book是主表,class是從表
EXPLAIN SELECT * FROM book LEFT JOIN class ON class.card = book.card;
結論:book表用上了索引,但是掃描的行數還是20行,因爲book表是主表(在left join左邊),所以會全表掃描
left join改爲innor join
# INNER JOIN:兩張表沒有主從的關係了
EXPLAIN SELECT * FROM book INNER JOIN class ON class.card = book.card;
結論:INNER JOIN查詢時,兩張表沒有主從的關係,book表完全使用了索引,只掃描了一行;所以,兩張表存在主從關係時(LEFT JOIN、RIGHT JOIN),給從表加索引效果會很有效,給主表加索引效果不明顯;
案例2
刪除一部分數據
delete from class where id<5;
explain分析:
# 注意:此時從表book表有索引,主表class表沒有索引
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
可以看到第二行的 type 變爲了 ref,rows 也變成了優化比較明顯。
這是由左連接特性決定的。LEFT JOIN 條件用於確定如何從右表搜索行,左邊(主表)一定都有,所以右邊是我們的關鍵點,一定需要建立索引。
刪除book索引
DROP INDEX Y ON book;
給class表新建索引
ALTER TABLE class ADD INDEX X (card);
explain
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
結論:給主表建索引,雖然能用上,但是不起大作用;
總結
- 保證被驅動表的join字段已經被索引
- left join 時,選擇小表作爲驅動表(全表掃描),大表作爲被驅動表
- inner join 時,mysql會自己幫你把小結果集的表選爲驅動表。
- 子查詢儘量不要放在被驅動表,有可能使用不到索引。
- 能夠直接多表關聯的儘量直接關聯,不用子查詢。
8.4.4 子查詢優化
儘量不要使用not in 或者 not exists
案例:查詢所有不爲掌門人的員工,按年齡分組 ,每個年齡段多少人
SELECT SQL_NO_CACHE age,count(*) FROM emp a WHERE id NOT IN(SELECT ceo FROM dept b2 WHERE ceo IS NOT NULL)
group by age having count(*)<10000
優化:用left outer join on xxx is null 替代
EXPLAIN SELECT SQL_NO_CACHE age,count(*) FROM emp a LEFT OUTER JOIN dept b ON a.id =b.ceo WHERE b.ceo IS NULL
group by age having count(*)<10000
8.4.5 排序分組優化
8.4.5.1 case
案例1
建立索引
create index idx_age_deptid_name on emp (age,deptid,name);
以下兩條查詢語句是否能使用到索引,能否去掉using filesort(出現using filesort說明查詢語句需要優化)
1、explain select SQL_NO_CACHE * from emp order by age,deptid;
沒有用到索引:
2、explain select SQL_NO_CACHE * from emp order by age,deptid limit 10;
用上了索引:
總結:無過濾 不索引(即不加過濾條件時,如limit,就不會用到索引)
案例2
建索引
create index idx_age_deptId_name on emp(age,deptId,name);
分析下邊的sql
# 用到索引,沒出現using filesort
1、explain select * from emp where age=45 order by deptid;
# 用到索引,沒出現using filesort
2、explain select * from emp where age=45 order by deptid,name;
# 沒用到索引,出現using filesort
3、explain select * from emp where age=45 order by deptid,empno;
# 沒用到索引,出現using filesort
4、explain select * from emp where age=45 order by name,deptid;
# 沒用到索引,出現using filesort
5、 explain select * from emp where deptid=45 order by age;
結論:順序錯,必排序(創建索引的順序是:age,deptId,name,而上邊第四條查詢順序是age、name、deptId,所以沒有用到索引,出現了using filesort排序)
案例3
# 都是降序
1、explain select * from emp where age=45 order by deptid desc, name desc ;
用到了索引:
# 一個降序,一個升序
2、explain select * from emp where age=45 order by deptid asc, name desc ;
沒有用到索引:
結論:方向反 必排序(即一個升序,一個降序時,一定會出現using filesort排序,不會使用索引)
8.4.5.2 ORDER BY子句,儘量使用Index方式排序,避免使用FileSort方式排序
8.4.5.3 索引的選擇
先清除emp上的索引,只留主鍵
案例:查詢年齡爲30歲的,且員工編號小於101000的用戶,按用戶名稱排序
SELECT SQL_NO_CACHE * FROM emp WHERE age =30 AND empno <101000 ORDER BY NAME ;
很顯然,type 是 ALL,即最壞的情況。Extra 裏還出現了 Using filesort,也是最壞的情況。優化是必須的。
開始優化:
思路: 儘量讓where的過濾條件和排序使用上索引,但是一共兩個字段(deptno,empno)上有過濾條件,一個字段(ename)有索引
1、我們建一個三個字段的組合索引可否?
CREATE INDEX idx_age_empno_name ON emp(age,empno,NAME);
我們發現using filesort 依然存在,所以name 並沒有用到索引。原因是因爲empno是一個範圍過濾,所以索引後面的字段不會再使用索引了。
所以我們建一個3值索引是沒有意義的 ,那麼我們先刪掉這個索引:
DROP INDEX idx_age_empno_name ON emp
爲了去掉filesort我們可以把索引建成:
CREATE INDEX idx_age_name ON emp(age,NAME);
也就是說empno 和name這個兩個字段我只能二選其一。 這樣我們優化掉了 using filesort。
執行一下sql:
速度果然提高了4倍。
但是
如果我們選擇那個範圍過濾,而放棄排序上的索引呢?建立 索引:
# 刪除舊索引
DROP INDEX idx_age_name ON emp;
# 建立新索引
create index idx_age_eno on emp(age,empno);
果然出現了filesort,而且type還是range光看字面其實並不美好。
我們來執行以下sql:
結果竟然有 filesort的 sql 運行速度,超過了已經優化掉 filesort的 sql ,而且快了好多倍。何故?
原因是所有的排序都是在條件過濾之後才執行的,所以如果條件過濾了大部分數據的話,幾百幾千條數據進行排序其實並不是很消耗性能,即使索引優化了排序但實際提升性能很有限。 相對的 empno<101000 這個條件如果沒有用到索引的話,要對幾萬條的數據進行掃描,這是非常消耗性能的,所以索引放在這個字段上性價比最高,是最優選擇。
結論: 當範圍條件和group by 或者 order by 的字段出現二選一時 ,優先觀察條件字段的過濾數量,如果過濾的數據足夠多,而需要排序的數據並不多時,優先把索引放在範圍字段上。反之,亦然。
8.4.5.4 如果不在索引列上,filesort有兩種算法:mysql就要啓動雙路排序和單路排序
雙路排序
MySQL 4.1之前是使用雙路排序,字面意思就是兩次掃描磁盤,最終得到數據,讀取行指針和orderby列,對他們進行排序,然後掃描已經排序好的列表,按照列表中的值重新從列表中讀取對應的數據輸出;即進行了兩次IO操作,排序的時候I\O一次,從列表裏取的時候又I\O了一次;從磁盤取排序字段,在buffer進行排序,再從磁盤取其他字段。取一批數據,要對磁盤進行了兩次掃描,衆所周知,I\O是很耗時的,所以在mysql4.1之後,出現了第二種改進的算法,就是單路排序。
單路排序
從磁盤讀取查詢需要的所有列,按照order by列在buffer對它們進行排序,然後掃描排序後的列表進行輸出,它的效率更快一些,避免了第二次讀取數據。並且把隨機IO變成了順序IO,但是它會使用更多的空間,因爲它把每一行都保存在內存中了。
結論及引申出的問題
由於單路是後出的,總體而言好過雙路,但是用單路有問題;
在sort_buffer中,方法B比方法A要多佔用很多空間,因爲方法B是把所有字段都取出, 所以有可能取出的數據的總大小超出了sort_buffer的容量,導致每次只能取sort_buffer容量大小的數據,進行排序(創建tmp文件,多路合併),排完再取取sort_buffer容量大小,再排……從而多次I/O。
本來想省一次I/O操作,反而導致了大量的I/O操作,反而得不償失。
優化策略
增大sort_buffer_size參數的設置
增大max_length_for_sort_data參數的設置
減少select 後面的查詢的字段。
做上邊這些優化策略的理由:
提高Order By的速度
-
Order by時select * 是一個大忌只Query需要的字段, 這點非常重要。在這裏的影響是:
1.1 當Query的字段大小總和小於max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 類型時,會用改進後的算法——單路排序, 否則用老算法——多路排序。
1.2 兩種算法的數據都有可能超出sort_buffer的容量,超出之後,會創建tmp文件進行合併排序,導致多次I/O,但是用單路排序算法的風險會更大一些,所以要提高sort_buffer_size。 -
嘗試提高 sort_buffer_size
不管用哪種算法,提高這個參數都會提高效率,當然,要根據系統的能力去提高,因爲這個參數是針對每個進程的 1M-8M之間調整 -
嘗試提高 max_length_for_sort_data
提高這個參數, 會增加用改進算法的概率。但是如果設的太高,數據總容量超出sort_buffer_size的概率就增大,明顯症狀是高的磁盤I/O活動和低的處理器使用率. 1024-8192之間調整
8.4.5.5 GROUP BY關鍵字優化
group by 使用索引的原則幾乎跟order by一致 ,唯一區別是groupby 即使沒有過濾條件用到索引,也可以直接使用索引。
8.4.6 最後使用索引的手段:覆蓋索引
覆蓋索引
簡單說就是,select 到 from 之間查詢的列 <=使用的索引列+主鍵
explain select * from emp where name like '%abc';
使用覆蓋索引後
九 查詢截取分析
9.1 慢查詢日誌
9.1.1 介紹
MySQL的慢查詢日誌是MySQL提供的一種日誌記錄,它用來記錄在MySQL中響應時間超過閾值的語句,具體指運行時間超過long_query_time值的SQL,則會被記錄到慢查詢日誌中。
具體指運行時間超過long_query_time值的SQL,則會被記錄到慢查詢日誌中。long_query_time的默認值爲10,意思是運行10秒以上的語句。
由他來查看哪些SQL超出了我們的最大忍耐時間值,比如一條sql執行超過5秒鐘,我們就算慢SQL,希望能收集超過5秒的sql,結合之前explain進行全面分析。
9.1.2 使用
介紹
默認情況下,MySQL數據庫沒有開啓慢查詢日誌,需要我們手動來設置這個參數。
當然,如果不是調優需要的話,一般不建議啓動該參數,因爲開啓慢查詢日誌會或多或少帶來一定的性能影響。慢查詢日誌支持將日誌記錄寫入文件;
查看是否開啓及如何開啓
SHOW VARIABLES LIKE '%slow_query_log%';
默認情況下slow_query_log的值爲OFF,表示慢查詢日誌是禁用的,可以通過設置slow_query_log的值來開啓
使用set global slow_query_log=1;
開啓了慢查詢日誌,只對當前數據庫生效,如果MySQL重啓後則會失效。
set global slow_query_log=1;
全局變量設置,對當前連接不影響:
對當前連接立刻生效:
如果要永久生效,就必須修改配置文件my.cnf(其它系統變量也是如此)
修改my.cnf文件,[mysqld]下增加或修改參數
slow_query_log 和slow_query_log_file後,然後重啓MySQL服務器。也即將如下兩行配置進my.cnf文件
slow_query_log =1
slow_query_log_file=/var/lib/mysql/atguigu-slow.log
關於慢查詢的參數slow_query_log_file ,它指定慢查詢日誌文件的存放路徑,系統默認會給一個缺省的文件host_name-slow.log(如果沒有指定參數slow_query_log_file的話)
那麼開啓了慢查詢日誌後,什麼樣的SQL纔會記錄到慢查詢日誌裏面呢?
這個是由參數long_query_time控制,默認情況下long_query_time的值爲10秒,命令:
SHOW VARIABLES LIKE 'long_query_time%';
可以使用命令修改,也可以在my.cnf參數裏面修改(my.cnf參數的方式後邊會講)
命令修改:
# 查出1秒就是慢sql
set long_query_time=1
假如運行時間正好等於long_query_time的情況,並不會被記錄下來。也就是說,
在mysql源碼裏是判斷大於long_query_time,而非大於等於。
案例
查看當前多少秒算慢
SHOW VARIABLES LIKE 'long_query_time%';
設置慢的闕值時間
# 修改爲闕值到1秒鐘的就是慢sql
set long_query_time=1
記錄慢SQL並後續分析:
實現一條慢sql
跟蹤日誌信息
查詢當前系統中有多少條慢查詢記錄:
show global status like '%Slow_queries%';
配置版
my.cnf
【mysqld】下配置:
slow_query_log=1;
slow_query_log_file=/var/lib/mysql/atguigu-slow.log
long_query_time=3;
log_output=FILE
9.1.3 日誌分析工具mysqldumpslow
9.1.3.1 查看mysqldumpslow的幫助信息
mysqldumpslow --help
-s: 是表示按照何種方式排序;
c: 訪問次數
l: 鎖定時間
r: 返回記錄
t: 查詢時間
al:平均鎖定時間
ar:平均返回記錄數
at:平均查詢時間
-t:即爲返回前面多少條的數據;
-g:後邊搭配一個正則匹配模式,大小寫不敏感的;
9.1.3.2 工作常用參考
得到返回記錄集最多的10個SQL
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log
得到訪問次數最多的10個SQL
mysqldumpslow -s c -t 10 /var/lib/mysql/atguigu-slow.log
得到按照時間排序的前10條裏面含有左連接的查詢語句
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/atguigu-slow.log
另外建議在使用這些命令時結合 | 和more 使用 ,否則有可能出現爆屏情況
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log | more
9.2 SHOW PROCESSLIST
能幹什麼
查詢所有用戶正在幹什麼,如果出現不順眼的,kill [id]殺掉進程即可
十 主從複製
10.1 複製的基本原理
slave會從master讀取binlog來進行數據同步
分三步:
- master將改變記錄到二進制日誌(binary log)。這些記錄過程叫做二進制日誌事件,binary log events
- slave將master的binary log events拷貝到它的中繼日誌(relay log)
- slave重做中繼日誌中的事件,將改變應用到自己的數據庫中。 MySQL複製是異步的且串行化的
原理圖:
10.2 複製的基本原則
- 每個slave只有一個master
- 每個slave只能有一個唯一的服務器ID
- 每個master可以有多個salve
10.3 複製的最大問題
延時
10.4 一主一從常見配置
此處使用的MySQL是5.5版本
10.4.1 配置的位置
主從都配置在[mysqld]結點下,都是小寫
10.4.2 主機修改my.ini配置文件
主服務器唯一ID
server-id=1
啓用二進制日誌
log-bin=自己本地的路徑/data/mysqlbin
log-bin=D:/devSoft/MySQLServer5.5/data/mysqlbin
設置不要複製的數據庫
binlog-ignore-db=mysql
設置需要複製的數據庫
binlog-do-db=需要複製的主數據庫名字(設置一個之前沒有的數據庫)
設置logbin格式
binlog_format=STATEMENT(默認)
也可以配置:binlog_format=ROW
10.4.3 mysql主從複製起始時,從機不繼承主機數據
10.4.4 從機配置文件修改my.cnf的[mysqld]欄位下
server-id = 2(注意my.cnf 中有server-id = 1)
relay-log=mysql-relay
10.4.5 因修改過配置文件,請主機+從機都重啓後臺mysql服務
10.4.6 主機從機都關閉防火牆
windows手動關閉
安全工具關上:騰訊管家,360等
關閉虛擬機linux防火牆
service iptables stop
10.4.7 在Windows主機上建立帳戶並授權slave
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'從機器數據庫IP' IDENTIFIED BY '123456';
查詢master的狀態
show master status
記錄下File和Position的值
執行完此步驟後不要再操作主服務器MYSQL,防止主服務器狀態值變化
10.4.8 在Linux從機上配置需要複製的主機
CHANGE MASTER TO MASTER_HOST='主機IP',
MASTER_USER='zhangsan',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='File名字',
MASTER_LOG_POS=Position數字;
啓動從服務器複製功能
start slave;
show slave status\G
上邊兩個參數都是Yes,則說明主從配置成功!
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
10.4.9 配置完畢
此時主機新建庫、新建表、insert記錄,從機複製
如何停止從服務複製功能
stop slave;