需求:html
通常的應用系統,讀寫比例在10:1左右,並且插入操做和通常的更新操做不多出現性能問題,在生產環境中,咱們遇到最多的,也是最容易出問題的,仍是一些複雜的查詢操做,所以對查詢語句的優化顯然是重中之重。
提及加速查詢,就不得不提到索引了。mysql索引:算法
簡單的說,至關於圖書的目錄,能夠幫助用戶快速的找到須要的內容.sql
在MySQL中也叫作「鍵」,是存儲引擎用於快速找到記錄的一種數據結構。可以大大提升查詢效率。特別是當數據量很是大,查詢涉及多個表時,使用索引每每能使查詢速度加快成千上萬倍. 數據庫
本質:服務器
索引本質:經過不斷地縮小想要獲取數據的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,咱們能夠老是用同一種查找方式來鎖定數據。數據結構
1. B+TREE 索引ide
B+樹是一種經典的數據結構,由平衡樹和二叉查找樹結合產生,它是爲磁盤或其它直接存取輔助設備而設計的一種平衡查找樹,在B+樹中,全部的記錄節點都是按鍵值大小順序存放在同一層的葉節點中,葉節點間用指針相連,構成雙向循環鏈表,非葉節點(根節點、枝節點)只存放鍵值,不存放實際數據。下面看一個2層B+樹的例子:函數
注意:一般其高度都在2~3層,查詢時能夠有效減小IO次數。性能
系統從磁盤讀取數據到內存時是以磁盤塊(block)爲基本單位的,位於同一磁盤塊中的數據會被一次性讀取出來,而不是按需讀取。InnoDB 存儲引擎使用頁做爲數據讀取單位,頁是其磁盤管理的最小單位,默認 page 大小是 16kB。
b+樹的查找過程
如圖所示,若是要查找數據項30,那麼首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找肯定30在28和65之間,鎖定磁盤塊1的P2指針,內存時間由於很是短(相比磁盤的IO)能夠忽略不計,經過磁盤塊1的P2指針的磁盤地址把磁盤塊由磁盤加載到內存,發生第二次IO,30在28和35之間,鎖定當前磁盤塊的P1指針,經過指針加載磁盤塊到內存,發生第三次IO,同時內存中作二分查找找到30,結束查詢,總計三次IO。真實的狀況是,3層的b+樹能夠表示上百萬的數據,若是上百萬的數據查找只須要三次IO,性能提升將是巨大的,若是沒有索引,每一個數據項都要發生一次IO,那麼總共須要百萬次的IO,顯然成本很是很是高。
強烈注意: 索引字段要儘可能的小,磁盤塊能夠存儲更多的索引.
2. HASH 索引
hash就是一種(key=>value)形式的鍵值對,容許多個key對應相同的value,但不容許一個key對應多個value,爲某一列或幾列創建hash索引,就會利用這一列或幾列的值經過必定的算法計算出一個hash值,對應一行或幾行數據. hash索引能夠一次定位,不須要像樹形索引那樣逐層查找,所以具備極高的效率.
假設索引使用hash函數f( ),以下:
1234f(
'Arjen'
) = 2323
f(
'Baron'
) = 7437
f(
'Peter'
) = 8784
f(
'Vadim'
) = 2458
此時,索引的結構大概以下:
3.HASH與BTREE比較:
hash類型的索引:查詢單條快,範圍查詢慢 btree類型的索引:b+樹,層數越多,數據量越大,範圍查詢和隨機查詢快(innodb默認索引類型) 不一樣的存儲引擎支持的索引類型也不同 InnoDB 支持事務,支持行級別鎖定,支持 Btree、Hash 等索引,不支持Full-text 索引; MyISAM 不支持事務,支持表級別鎖定,支持 Btree、Full-text 等索引,不支持 Hash 索引; Memory 不支持事務,支持表級別鎖定,支持 Btree、Hash 等索引,不支持 Full-text 索引; NDB 支持事務,支持行級別鎖定,支持 Hash 索引,不支持 Btree、Full-text 等索引; Archive 不支持事務,支持表級別鎖定,不支持 Btree、Hash、Full-text 等索引;
MySQL中常見索引有:
- 普通索引
- 惟一索引
- 主鍵索引
- 組合索引
1.普通索引
普通索引僅有一個功能:加速查詢
建立表+索引#建立表同時添加name字段爲普通索引 create table tb1( id int not null auto_increment primary key, name varchar(100) not null, index idx_name(name) );建立索引#單獨爲表指定普通索引 create index idx_name on tb1(name);刪除索引drop index idx_name on tb1;查看索引show index from tb1;
查看索引 列介紹1、Table 表的名稱。 2、 Non_unique 若是索引爲惟一索引,則爲0,若是能夠則爲1。 3、 Key_name 索引的名稱 4、 Seq_in_index 索引中的列序列號,從1開始。 5、 Column_name 列名稱。 6、 Collation 列以什麼方式存儲在索引中。在MySQL中,有值‘A’(升序)或NULL(無分類)。 7、Cardinality 索引中惟一值的數目的估計值。 8、Sub_part 若是列只是被部分地編入索引,則爲被編入索引的字符的數目。若是整列被編入索引,則爲NULL。 9、 Packed 指示關鍵字如何被壓縮。若是沒有被壓縮,則爲NULL。 10、 Null 若是列含有NULL,則含有YES。若是沒有,則該列含有NO。 11、 Index_type 用過的索引方法(BTREE, FULLTEXT, HASH, RTREE)。 十二、 Comment 多種評註
2.惟一索引
惟一索引有兩個功能:加速查詢 和 惟一約束(可含一個null 值)
建立表+惟一(unique)索引create table tb2( id int not null auto_increment primary key, name varchar(50) not null, age int not null, unique index idx_age (age) )建立unique索引create unique index idx_age on tb2(age);
3.主鍵索引
主鍵有兩個功能:加速查詢 和 惟一約束(不可含null)
注意:一個表中最多隻能有一個主鍵索引
建立表 + 建立主鍵#方式一: create table tb3( id int not null auto_increment primary key, name varchar(50) not null, age int default 0 ); #方式二: create table tb3( id int not null auto_increment, name varchar(50) not null, age int default 0 , primary key(id) );建立主鍵alter table tb3 add primary key(id);刪除主鍵#方式一 alter table tb3 drop primary key; #方式二: #若是當前主鍵爲自增主鍵,則不能直接刪除.須要先修改自增屬性,再刪除 alter table tb3 modify id int ,drop primary key;4.組合索引
組合索引是將n個列組合成一個索引
其應用場景爲:頻繁的同時使用n列來進行查詢,如:where n1 = 'alex' and n2 = 666。
建立表+組合索引create table tb4( id int not null , name varchar(50) not null, age int not null, index idx_name_age (name,age) )建立組合索引create index idx_name_age on tb4(name,age);索引應用場景舉個例子來講,好比你在爲某商場作一個會員卡的系統。 這個系統有一個會員表 有下列字段: 會員編號 INT 會員姓名 VARCHAR(10) 會員身份證號碼 VARCHAR(18) 會員電話 VARCHAR(10) 會員住址 VARCHAR(50) 會員備註信息 TEXT 那麼這個 會員編號,做爲主鍵,使用 PRIMARY 會員姓名 若是要建索引的話,那麼就是普通的 INDEX 會員身份證號碼 若是要建索引的話,那麼能夠選擇 UNIQUE (惟一的,不容許重複)
數據庫中的B+樹索引能夠分爲彙集索引和輔助索引.
彙集索引:InnoDB表 索引組織表,即表中數據按主鍵B+樹存放,葉子節點直接存放整條數據,每張表只能有一個彙集索引。
如圖:
1.當你定義一個主鍵時,InnnodDB存儲引擎則把它當作彙集索引
2.若是你沒有定義一個主鍵,則InnoDB定位到第一個惟一索引,且該索引的全部列值均飛空的,則將其當作彙集索引。
3若是表沒有主鍵或合適的惟一索引INNODB會產生一個隱藏的行ID值6字節的行ID彙集索引,
補充:因爲實際的數據頁只能按照一顆B+樹進行排序,所以每張表只能有一個彙集索引,彙集索引對於主鍵的排序和範圍查找很是有利.
輔助索引:(也稱非彙集索引)是指葉節點不包含行的所有數據,葉節點除了包含鍵值以外,還包含一個書籤鏈接,經過該書籤再去找相應的行數據。下圖顯示了
InnoDB存儲引擎輔助索引得到數據的查找方式:
從上圖中能夠看出,輔助索引葉節點存放的是主鍵值,得到主鍵值後,再從彙集索引中查找整行數據。舉個例子,若是在一顆高度爲3的輔助索引中查找數據,首先從輔助索引中得到主鍵值(3次IO),接着從高度爲3的彙集索引中查找以得到整行數據(3次IO),總共需6次IO。一個表上能夠存在多個輔助索引。總結兩者區別:
相同的是:無論是彙集索引仍是輔助索引,其內部都是B+樹的形式,即高度是平衡的,葉子結點存放着全部的數據。
不一樣的是:彙集索引葉子結點存放的是一整行的信息,而輔助索引葉子結點存放的是單個索引列信息.
什麼時候使用匯集索引或非彙集索引
下面的表總結了什麼時候使用匯集索引或非彙集索引(很重要):
動做描述
使用匯集索引
使用非彙集索引
列常常被分組排序
應
應
返回某範圍內的數據
應
不該
一個或極少不一樣值
不該
不該
頻繁更新的列
不該
應
外鍵列
應
應
主鍵列
應
應
頻繁修改索引列
不該
應
1.建立數據
建立表-- 1.建立表 CREATE TABLE userInfo( id int NOT NULL, name VARCHAR(16) DEFAULT NULL, age int, sex char(1) not null, email varchar(64) default null )ENGINE=MYISAM DEFAULT CHARSET=utf8;
注意:MYISAM存儲引擎 不產生引擎事務,數據插入速度極快,爲方便快速插入測試數據,等咱們插完數據,再把存儲類型修改成InnoDB
2.建立存儲過程,插入數據
建立存儲過程-- 2.建立存儲過程 delimiter$$ CREATE PROCEDURE insert_user_info(IN num INT) BEGIN DECLARE val INT DEFAULT 0; DECLARE n INT DEFAULT 1; -- 循環進行數據插入 WHILE n <= num DO set val = rand()*50; INSERT INTO userInfo(id,name,age,sex,email)values(n,concat('alex',val),rand()*50,if(val%2=0,'女','男'),concat('alex',n,'@qq.com')); set n=n+1; end while; END $$ delimiter;
3.調用存儲過程,插入500萬條數據
1call insert_user_info(5000000);
4.此步驟能夠忽略。修改引擎爲INNODB
1ALTER
TABLE
userinfo ENGINE=INNODB;
5.測試索引
1. 在沒有索引的前提下測試查詢速度
1SELECT
*
FROM
userinfo
WHERE
id = 4567890;
注意:無索引狀況,mysql根本就不知道id等於4567890的記錄在哪裏,只能把數據表從頭至尾掃描一遍,此時有多少個磁盤塊就須要進行多少IO操做,因此查詢速度很慢.2.在表中已經存在大量數據的前提下,爲某個字段段創建索引,創建速度會很慢
1CREATE
INDEX
idx_id
on
userinfo(id);
3.在索引創建完畢後,以該字段爲查詢條件時,查詢速度提高明顯
1select
*
from
userinfo
where
id = 4567890;
注意:
1. mysql先去索引表裏根據b+樹的搜索原理很快搜索到id爲4567890的數據,IO大大下降,於是速度明顯提高
2. 咱們能夠去mysql的data目錄下找到該表,能夠看到添加索引後該表佔用的硬盤空間多了
3.若是使用沒有添加索引的字段進行條件查詢,速度依舊會很慢(如圖:)
數據庫表中添加索引後確實會讓查詢速度起飛,但前提必須是正確的使用索引來查詢,若是以錯誤的方式使用,則即便創建索引也會不奏效。
即便創建索引,索引也不會生效,例如:#1. 範圍查詢(>、>=、<、<=、!= 、between...and) #1. = 等號 select count(*) from userinfo where id = 1000 -- 執行索引,索引效率高 #2. > >= < <= between...and 區間查詢 select count(*) from userinfo where id <100; -- 執行索引,區間範圍越小,索引效率越高 select count(*) from userinfo where id >100; -- 執行索引,區間範圍越大,索引效率越低 select count(*) from userinfo where id between 10 and 500000; -- 執行索引,區間範圍越大,索引效率越低 #3. != 不等於 select count(*) from userinfo where id != 1000; -- 索引範圍大,索引效率低 #2.like '%xx%' #爲 name 字段添加索引 create index idx_name on userinfo(name); select count(*) from userinfo where name like '%xxxx%'; -- 全模糊查詢,索引效率低 select count(*) from userinfo where name like '%xxxx'; -- 以什麼結尾模糊查詢,索引效率低 #例外: 當like使用以什麼開頭會索引使用率高 select * from userinfo where name like 'xxxx%'; #3. or select count(*) from userinfo where id = 12334 or email ='xxxx'; -- email不是索引字段,索引此查詢全表掃描 #例外:當or條件中有未創建索引的列才失效,如下會走索引 select count(*) from userinfo where id = 12334 or name = 'alex3'; -- id 和 name 都爲索引字段時, or條件也會執行索引 #4.使用函數 select count(*) from userinfo where reverse(name) = '5xela'; -- name索引字段,使用函數時,索引失效 #例外:索引字段對應的值可使用函數,咱們能夠改成一下形式 select count(*) from userinfo where name = reverse('5xela'); #5.類型不一致 #若是列是字符串類型,傳入條件是必須用引號引發來,否則... select count(*) from userinfo where name = 454; #類型一致 select count(*) from userinfo where name = '454'; #6.order by #排序條件爲索引,則select字段必須也是索引字段,不然沒法命中 select email from userinfo ORDER BY name DESC; -- 沒法命中索引 select name from userinfo ORDER BY name DESC; -- 命中索引 #特別的:若是對主鍵排序,則仍是速度很快: select id from userinfo order by id desc;
組合索引: 是指對錶上的多個列組合起來作一個索引.
組合索引好處:簡單的說有兩個主要緣由:
- "一個頂三個"。建了一個(a,b,c)的組合索引,那麼實際等於建了(a),(a,b),(a,b,c)三個索引,由於每多一個索引,都會增長寫操做的開銷和磁盤空間的開銷。對於大量數據的表,這但是不小的開銷!
- 索引列越多,經過索引篩選出的數據越少。有1000W條數據的表,有以下sql:select * from table where a = 1 and b =2 and c = 3,假設假設每一個條件能夠篩選出10%的數據,若是隻有單值索引,那麼經過該索引能篩選出1000W*10%=100w 條數據,而後再回表從100w條數據中找到符合b=2 and c= 3的數據,而後再排序,再分頁;若是是組合索引,經過索引篩選出1000w *10% *10% *10%=1w,而後再排序、分頁,哪一個更高效,一眼便知
最左匹配原則: 從左往右依次使用生效,若是中間某個索引沒有使用,那麼斷點前面的索引部分起做用,斷點後面的索引沒有起做用;select * from mytable where a=3 and b=5 and c=4; #abc三個索引都在where條件裏面用到了,並且都發揮了做用
select * from mytable where c=4 and b=6 and a=3; #這條語句列出來只想說明 mysql沒有那麼笨,where裏面的條件順序在查詢以前會被mysql自動優化,效果跟上一句同樣
select * from mytable where a=3 and c=7; #a用到索引,b沒有用,因此c是沒有用到索引效果的
select * from mytable where a=3 and b>7 and c=3; #a用到了,b也用到了,c沒有用到,這個地方b是範圍值,也算斷點,只不過自身用到了索引
select * from mytable where b=3 and c=4; #由於a索引沒有使用,因此這裏 bc都沒有用上索引效果
select * from mytable where a>4 and b=7 and c=9; #a用到了 b沒有使用,c沒有使用
select * from mytable where a=3 order by b; #a用到了索引,b在結果排序中也用到了索引的效果
select * from mytable where a=3 order by c; #a用到了索引,可是這個地方c沒有發揮排序效果,由於中間斷點了
select * from mytable where b=3 order by a; #b沒有用到索引,排序中a也沒有發揮索引效果
123456789101. 避免使用
select
*
2. 其餘數據庫中使用
count
(1)或
count
(列) 代替
count
(*),而mysql數據庫中
count
(*)通過優化後,效率與前兩種基本同樣.
3. 建立表時儘可能時
char
代替
varchar
4. 表的字段順序固定長度的字段優先
5. 組合索引代替多個單列索引(常用多個條件查詢時)
6. 使用鏈接(
JOIN
)來代替子查詢(Sub-Queries)
7. 不要有超過4個以上的錶鏈接(
JOIN
)
8. 優先執行那些可以大量減小結果的鏈接。
9. 連表時注意條件類型需一致
10.索引散列值不適合建索引,例:性別不適合
explain + 查詢SQL - 用於顯示SQL執行信息參數,根據參考信息能夠進行SQL優化
1explain
select
count
(*)
from
userinfo
where
id = 1;
執行計劃:讓mysql預估執行操做(通常正確) type : 查詢計劃的鏈接類型, 有多個參數,先從最佳類型到最差類型介紹
性能: null > system/const > eq_ref > ref > ref_or_null > index_merge > range > index > all 慢: explain select * from userinfo where email='alex'; type: ALL(全表掃描) 特別的: select * from userinfo limit 1; 快: explain select * from userinfo where name='alex'; type: ref(走索引)EXPLAIN 參數詳解: http://www.cnblogs.com/wangfengming/articles/8275448.html
慢查詢日誌
將mysql服務器中影響數據庫性能的相關SQL語句記錄到日誌文件,經過對這些特殊的SQL語句分析,改進以達到提升數據庫性能的目的。
慢查詢日誌參數:
12345long_query_time : 設定慢查詢的閥值,超出設定值的SQL即被記錄到慢查詢日誌,缺省值爲10s
slow_query_log : 指定是否開啓慢查詢日誌
log_slow_queries : 指定是否開啓慢查詢日誌(該參數已經被slow_query_log取代,作兼容性保留)
slow_query_log_file : 指定慢日誌文件存放位置,能夠爲空,系統會給一個缺省的文件host_name-slow.log
log_queries_not_using_indexes: 若是值設置爲
ON
,則會記錄全部沒有利用索引的查詢.
查看 MySQL慢日誌信息
1234#.查詢慢日誌配置信息 :
show variables
like
'%query%'
;
#.修改配置信息
set
global
slow_query_log =
on
;
查看不使用索引參數狀態:
1234# 顯示參數
show variables
like
'%log_queries_not_using_indexes'
;
# 開啓狀態
set
global
log_queries_not_using_indexes =
on
;
查看慢日誌顯示的方式
12345#查看慢日誌記錄的方式
show variables
like
'%log_output%'
;
#設置慢日誌在文件和表中同時記錄
set
global
log_output=
'FILE,TABLE'
;
測試慢查詢日誌
12345#查詢時間超過10秒就會記錄到慢查詢日誌中
select
sleep(3)
FROM
user
;
#查看錶中的日誌
select
*
from
mysql.slow_log;
執行此段代碼:
1select
*
from
userinfo limit 3000000,10;
優化方案:
一. 簡單粗暴,就是不容許查看這麼靠後的數據,好比百度就是這樣的
最多翻到72頁就不讓你翻了,這種方式就是從業務上解決;
二.在查詢下一頁時把上一頁的行id做爲參數傳遞給客戶端程序,而後sql就改爲了
1select
*
from
userinfo
where
id>3000000 limit 10;
這條語句執行也是在毫秒級完成的,id>300w其實就是讓mysql直接跳到這裏了,不用依次在掃描全面全部的行
若是你的table的主鍵id是自增的,而且中間沒有刪除和斷點,那麼還有一種方式,好比100頁的10條數據
1select
*
from
userinfo
where
id>100*10 limit 10;
三.最後第三種方法:延遲關聯
咱們在來分析一下這條語句爲何慢,慢在哪裏。
1select
*
from
userinfo limit 3000000,10;
玄機就處在這個 * 裏面,這個表除了id主鍵確定還有其餘字段 好比 name age 之類的,由於select * 因此mysql在沿着id主鍵走的時候要回行拿數據,走一下拿一下數據;
若是把語句改爲
1select
id
from
userinfo limit 3000000,10;
你會發現時間縮短了一半;而後咱們在拿id分別去取10條數據就好了;
語句就改爲這樣了:
1select
table
.*
from
userinfo
inner
join
(
select
id
from
userinfo limit 3000000,10 )
as
tmp
on
tmp.id=userinfo.id;
這三種方法最早考慮第一種 其次第二種,第三種是別無選擇