索引有它的缺點:雖然索引大大提高了查詢速度,同時卻會降低更新表的速度,如對錶進行INSERT、UPDATE和DELETE。因爲更新表時,MySQL不僅要保存數據,還要保存一下索引文件。建立索引會佔用磁盤空間的索引文件。一般情況這個問題不太嚴重,但如果你在一個大表上創建了多種組合索引,索引文件的會膨脹很快。索引只是提高效率的一個因素,如果你的MySQL有大數據量的表,就需要花時間研究建立最優秀的索引,或優化查詢語句。
建索引的幾大原則
1、最左前綴匹配原則,非常重要的原則
對於多列索引,總是從索引的最前面字段開始,接着往後,中間不能跳過。比如創建了多列索引(name,age,sex),會先匹配name字段,再匹配age字段,再匹配sex字段的,中間不能跳過。mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就停止匹配。比如a = 1 and b = 2 and c > 3 and d = 4,如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調整。
2、儘量選擇區分度高的列作爲索引
區分度的公式是count(distinct col)/count(*),表示字段不重複的比例,比例越大我們掃描的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,那可能有人會問,這個比例有什麼經驗值嗎?使用場景不同,這個值也很難確定,一般需要join的字段我們都要求是0.1以上,即平均1條掃描10條記錄。(比如,我們會選擇學號做索引,而不會選擇性別來做索引。)
3、=和in可以亂序
比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意順序,mysql的查詢優化器會幫你優化成索引可以識別的形式。
4、索引列不能參與計算,保持列“乾淨”
比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,需要把所有元素都應用函數才能比較,顯然成本太大。所以語句應該寫成create_time = unix_timestamp(’2014-05-29’);
例如:select * from users where YEAR(adddate)<2007,將在每個行上進行運算,這將導致索引失效而進行全表掃描,因此我們可以改成:select * from users where adddate<'2007-01-01'。
比如:Flistid+1>‘2000000608201108010831508721‘。原因很簡單,假如索引列參與計算的話,那每次檢索時,都會先將索引計算一次,再做比較,顯然成本太大。
5、儘量的擴展索引,不要新建索引
比如表中已經有a的索引,現在要加(a,b)的索引,那麼只需要修改原來的索引即可。
6. 索引不會包含有NULL值的列
只要列中包含有NULL值都將不會被包含在索引中,複合索引中只要有一列含有NULL值,那麼這一列對於此複合索引就是無效的。所以我們在數據庫設計時不要讓字段的默認值爲NULL。
7. 使用短索引
對串列進行索引,如果可能應該指定一個前綴長度。例如,如果有一個CHAR(255)的列,如果在前10個或20個字符內,多數值是惟一的,那麼就不要對整個列進行索引。短索引不僅可以提高查詢速度而且可以節省磁盤空間和I/O操作。
8. 索引列排序
MySQL查詢只使用一個索引,因此如果where子句中已經使用了索引的話,那麼order by中的列是不會使用索引的。因此數據庫默認排序可以符合要求的情況下不要使用排序操作;儘量不要包含多個列的排序,如果需要最好給這些列創建複合索引。
9. like語句操作
一般情況下不鼓勵使用like操作,如果非使用不可,如何使用也是一個問題。like “%aaa%” 不會使用索引而like “aaa%”可以使用索引。
最後總結一下,MySQL只對以下操作符才使用索引:<,<=,=,>,>=,between,in,以及某些時候的like(不以通配符%或_開頭的情形)。而理論上每張表裏面最多可創建16個索引,不過除非是數據量真的很多,否則過多的使用索引也不是那麼好玩的,比如我剛纔針對text類型的字段創建索引的時候,系統差點就卡死了。
使用索引優化查詢
使用索引的典型場景
1、匹配全值
對索引中所有列都指定具體值,即對索引中的所有列都有等值匹配的條件。
#設置組合索引(rental_date,inventory_id,customer_id)爲唯一索引。
EXPLAIN
SELECT
*
FROM
rental
WHERE rental_date = '2005-05-25 17:22:10'
AND inventory_id = 373
AND customer_id = 343 ;
2、匹配值的範圍查詢
對索引的值能夠進行範圍查找。
#設置索引idx_fk_customer_id(customer_id)
EXPLAIN
SELECT
*
FROM
rental
WHERE customer_id >= 373
AND customer_id < 400 ;
類型type爲range說明優化器選擇範圍查詢,索引key爲idx_fk_customer_id說明優化器選擇索引idx_fk_customer_id來加速訪問,Extra爲using where說明優化器除了利用索引加速訪問外,還需要根據索引回表查詢數據。
3、匹配最左前綴
僅僅使用索引中的最左邊列進行查詢。比如組合索引(col1,col2,col3)能夠被col1,col1+col2,col1+col2+col3的等值查詢利用到的。
#創建索引idx_payment_date(payment_date,amount,last_update);
EXPLAIN
SELECT
*
FROM
payment
WHERE payment_date = '2006-02-14 15:16:03'
AND last_update = '2006-02-15 22:12:32' ;
從結果可以看出利用了索引,但又row爲182行,所有隻使用了部分索引。
EXPLAIN
SELECT
*
FROM
payment
WHERE amount = 3.98
AND last_update = '2006-02-15' ;
從結果看出,這次查詢沒有利用索引,進行了全表查找。
4、僅對索引進行查詢
當查詢列都在索引字段中。即select中的列都在索引中。
EXPLAIN
SELECT
last_update
FROM
payment
WHERE payment_date = '2005-08-19 21:21:47'
AND amount = 4.99 ;
extra部分Using index,說明不需要通過索引回表,Using index就是平時說的覆蓋索引掃描(即找到索引,就找到了要查詢的結果,不用再回表查找了)。
5、匹配列前綴
僅僅使用索引的第一列,並且只包含索引第1列的開頭部分進行查找。
#創建索引idx_title_desc_part(title(10),description(20));
EXPLAIN
SELECT
title
FROM
film_text
WHERE title LIKE 'AFRICAN%' ;
6、索引部分等值匹配,部分範圍匹配
EXPLAIN
SELECT
inventory_id
FROM
rental
WHERE rental_date = '2006-02-14 15:16:03'
AND customer_id >= 300
AND customer_id <= 400 ;
type=ref,說明使用了索引。key爲idx_rental_date說明優化器選擇使用索引加速查詢,同時由於只查詢索引字段inventory_id,故Extra部分有using index,表示查詢使用了覆蓋索引掃描。
7、若列名是索引,則使用column_name is null就會使用索引
EXPLAIN
SELECT
*
FROM
payment
WHERE rental_id IS NULL ;
索引存在但不能使用索引的典型場景
1、以%開頭的like查詢
EXPLAIN
SELECT
*
FROM
actor
WHERE last_name LIKE '%NI%' ;
因爲B-Tree索引的結構,所以以%開頭的查詢自然沒法使用索引。InnoDB的表都是聚簇表,一般索引都會比表小,掃描索引比掃描表更快,而InnoDB表上二級索引idx_last_name實際上存儲字段last_name和主鍵actor_id,故先掃描二級索引idx_last_name獲得滿足條件last_name like '%NI%'的主鍵actor_id列表,之後根據主鍵回表去檢索記錄,這樣避免了全表掃面演員表actor產生的大量IO請求
#優化
EXPLAIN
SELECT
*
FROM
(SELECT
actor_id
FROM
actor
WHERE last_name LIKE '%NI%') a,
actor b
WHERE a.actor_id = b.actor_id ;
2、數據類型出現隱式轉化,不會使用索引
EXPLAIN
SELECT
*
FROM
actor
WHERE last_name = 1 ;
#使用索引
EXPLAIN
SELECT
*
FROM
actor
WHERE last_name = '1' ;
3、組合索引,不滿足最左原則,不使用符合索引
EXPLAIN
SELECT
*
FROM
payment
WHERE amount = 3.98
AND last_update = '2006-02-15 22:12:32' ;
4、估計使用索引比全表掃描還慢,則不要使用索引
update film_text set title=concat('S',title);
如查詢以“S”開頭的標題的電影,返回記錄比例比較大,mysql預估索引掃描還不如全表掃描。
EXPLAIN
SELECT
*
FROM
film_text
WHERE title LIKE 'S%' ;
5、用or分割條件,若or前後只要有一個列沒有索引,就都不會用索引
EXPLAIN
SELECT
*
FROM
payment
WHERE customer_id = 203
OR amount = 3.96 ;
應儘量避免在where子句中使用or來連接條件,否則將導致引擎放棄使用索引而進行全表掃描,如:
低效:select * from t_credit_detail where Flistid = '2000000608201108010831508721' or Flistid = '10000200001';
可以用下面這樣的查詢代替上面的 or 查詢:
高效:select from t_credit_detail where Flistid = '2000000608201108010831508721' union all select from t_credit_detail where Flistid = '10000200001';
6 使用!= 或 <> 操作符時
儘量避免使用!= 或 <>操作符,否則數據庫引擎會放棄使用索引而進行全表掃描。使用>或<會比較高效。
select * from t_credit_detail where Flistid != '2000000608201108010831508721'\G
7 對字段進行null值判斷
應儘量避免在where子句中對字段進行null值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如:
低效:select * from t_credit_detail where Flistid is null ;
可以在Flistid上設置默認值0,確保表中Flistid列沒有null值,然後這樣查詢:
高效:select * from t_credit_detail where Flistid =0;
避免select *
在解析的過程中,會將'*' 依次轉換成所有的列名,這個工作是通過查詢數據字典完成的,這意味着將耗費更多的時間。
所以,應該養成一個需要什麼就取什麼的好習慣。
優化子查詢
使用連接查詢(join)代替子查詢可以提高查詢效率。
MySQL從4.1版開始支持子查詢(一個查詢的結果作爲另一個select子句的條件),子查詢雖然靈活但執行效率不高,因爲使用子查詢時,MySQL需要爲內層查詢語句的查詢結果建立一個臨時表,然後外層查詢語句從臨時表中查詢記錄,查詢完畢後 再撤銷這些臨時表,因此子查詢的速度會相應的受到影響。而連接查詢不需要建立臨時表其查詢速度快於子查詢!
優化插入記錄的速度
innoDB引擎的表常見的優化方法
(1)、禁用唯一性檢查
插入數據時,MySQL會對插入的記錄進行唯一性校驗。這種唯一性校驗會降低插入記錄的速度。爲了降低這種情況對查詢速度的影響可以在插入記錄之前禁用唯一性檢查,等到記錄插入完畢後再開啓。
Set unique_check=0; 開啓唯一性檢查的語句如下:set unique_checks=1;
(2)、禁用外鍵檢查
插入數據之前禁止對外鍵的檢查,數據插入完成之後再恢復對外鍵的檢查。
Set foreign_key_checks=0; 恢復外鍵檢查:set foreign_key_checks=1;
(3)、禁止自動提交
插入數據之前禁止事務的自動提交,數據導入之後,執行恢復自動提交操作。
禁止自動提交的語句 set autocommit=0;恢復自動提交:set autocommit=1;
Myisam引擎表常見的優化方法
(1)、禁用索引 alter table table_name disable keys
導入數據 loading the data
開啓索引 alter table table_name enable keys
(2)禁用唯一性檢查
(3)使用批量插入
(4)當需要批量導入數據時,使用load data infile
優化insert語句
1. 一條SQL語句插入多條數據。
常用的插入語句如:
1 2 3 4 | INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) VALUES ('0', 'userid_0', 'content_0', 0); INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) VALUES ('1', 'userid_1', 'content_1', 1); |
修改成:
1 2 | INSERTINTO`insert_table`(`datetime`,`uid`,`content`,`type`) VALUES('0','userid_0','content_0',0),('1','userid_1','content_1',1); |
修改後的插入操作能夠提高程序的插入效率。這裏第二種SQL執行效率高的主要原因是合併後日志量(MySQL的binlog和innodb的事務讓日誌)減少了,降低日誌刷盤的數據量和頻率,從而提高效率。通過合併SQL語句,同時也能減少SQL語句解析的次數,減少網絡傳輸的IO。
這裏提供一些測試對比數據,分別是進行單條數據的導入與轉化成一條SQL語句進行導入,分別測試1百、1千、1萬條數據記錄。
2. 在事務中進行插入處理。
把插入修改成:
1 2 3 4 5 6 7 | START TRANSACTION; INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) VALUES ('0', 'userid_0', 'content_0', 0); INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) VALUES ('1', 'userid_1', 'content_1', 1); ... COMMIT; |
使用事務可以提高數據的插入效率,這是因爲進行一個INSERT操作時,MySQL內部會建立一個事務,在事務內才進行真正插入處理操作。通過使用事務可以減少創建事務的消耗,所有插入都在執行後才進行提交操作。
這裏也提供了測試對比,分別是不使用事務與使用事務在記錄數爲1百、1千、1萬的情況。
3. 數據有序插入。
數據有序的插入是指插入記錄在主鍵上是有序排列,例如datetime是記錄的主鍵:
1 2 3 4 5 6 | INSERTINTO`insert_table`(`datetime`,`uid`,`content`,`type`) VALUES('1','userid_1','content_1',1); INSERTINTO`insert_table`(`datetime`,`uid`,`content`,`type`) VALUES('0','userid_0','content_0',0); INSERTINTO`insert_table`(`datetime`,`uid`,`content`,`type`) VALUES('2','userid_2','content_2',2); |
修改成:
1 2 3 4 5 6 | INSERTINTO`insert_table`(`datetime`,`uid`,`content`,`type`) VALUES('0','userid_0','content_0',0); INSERTINTO`insert_table`(`datetime`,`uid`,`content`,`type`) VALUES('1','userid_1','content_1',1); INSERTINTO`insert_table`(`datetime`,`uid`,`content`,`type`) VALUES('2','userid_2','content_2',2); |
由於數據庫插入時,需要維護索引數據,無序的記錄會增大維護索引的成本。我們可以參照innodb使用的B+tree索引,如果每次插入記錄都在索引的最後面,索引的定位效率很高,並且對索引調整較小;如果插入的記錄在索引中間,需要B+tree進行分裂合併等處理,會消耗比較多計算資源,並且插入記錄的索引定位效率會下降,數據量較大時會有頻繁的磁盤操作。
優化order by語句
ORDER BY子句,儘量使用Index方式排序,避免使用FileSort方式排序
MySQL支持二種方式的排序,FileSort和Index,後者效率高,它指MySQL掃描索引本身完成排序。FileSort方式效率較低。
ORDER BY滿足以下情況,會使用Index方式排序:
a)ORDER BY 語句使用索引最左前列。參見第1句b)使用Where子句與Order BY子句條件列組合滿足索引最左前列。參見第2句.
以下情況,會使用FileSort方式的查詢
a)檢查的行數過多,且沒有使用覆蓋索引。
第3句,雖然跟第2句一樣,order by使用了索引最左前列uid,但依然使用了filesort方式排序,因爲status並不在索引中,所以沒辦法只掃描索引。
b)使用了不同的索引,MySQL每回只採用一個索引.
第4句,order by出現二個索引,分別是uid_fuid和聚集索引(pk)
c)對索引列同時使用了ASC和DESC。 通過where語句將order by中索引列轉爲常量,則除外。
第5句,和第6句在order by子句中,都出現了ASC和DESC排序,但是第5句卻使用了filesort方式排序,是因爲第6句where uid取出排序需要的數據,MySQL將其轉爲常量,它的ref列爲const。
d)where語句與order by語句,使用了不同的索引。參見第7句。e)where語句或者ORDER BY語句中索引列使用了表達式,包括函數表達式。參見第8,9句
f)where 語句與ORDER BY語句組合滿足最左前綴,但where語句中使用了條件查詢。
查見第10句,雖然where與order by構成了索引最左有綴的條件,但是where子句中使用的是條件查詢。
g)order by子句中加入了非索引列,且非索引列不在where子句中。h)order by或者它與where組合沒有滿足索引最左前列。
參見第11句和12句,where與order by組合,不滿足索引最左前列. (uid, fsex)跳過了fuid
i)當使用left join,使用右邊的表字段排序。
參見第13句,儘管user.uid是pk,依然會使用filesort排序。
FileSort排序算法
算法一:雙路排序算法
只利用ORDERBY子句中包括的列對象進行排序(適用於有BLOB、TEXT類型的列對象參與的排序)
MySQL4.1之前的排序算法,完整實現過程如下:
1) 按索引鍵或全表掃描的方式,讀取所有的元組,不匹配WHERE子句的元組被跳過;第一步需要從存儲讀入數據,引發I/O操作。
2) 對於每一行,在緩衝區中存儲一對值(對值,包括排序關鍵字和元組指針)。緩衝區的大小是系統變量的sort_buffer_size設定的值。
3) 當緩衝區已滿,運行快排算法(快速排序,qsort)對一個塊中的數據進行排序,將結果存儲在一個臨時文件。保存一個指向排序後的塊的指針(如果第二步所說的對值都能被緩衝區容納,則不會創建臨時文件)。
4) 重複上述步驟,直到所有的行已經被讀取。
5) 執行一個多路歸併操作(操作對象是第三步生成的每一個有序的塊)彙集到“MERGEBUFF域”,然後存放到在第二個臨時文件中。重複操作,直到第一個文件的所有塊歸併後存入到第二個文件;“MERGEBUFF域”是代碼sql_sort.h中定義的宏,值爲7。
6) 重複以下操作(第7步和第8步),直到留下少於“MERGEBUFF2域”標明的塊數爲止;“MERGEBUFF2域”是代碼sql_sort.h中定義的宏,值爲15。
7) 在最後一次多路歸併操作中,把元組的指針(排序關鍵字的最後部分)寫入到一個結果文件。
8) 在結果文件中,按照排列的順序使用元組指針讀取元組(爲了優化這項操作,MySQL讀入元組指針進入一個大的塊,對塊中元組指針進行排序而不是直接對數據排序,然後再用有序的元組指針獲取元組到元組緩存,元組緩衝區的大小由read_rnd_buffer_size參數控制)。第8步需要從存儲讀入數據,引發I/O操作。
算法二:單路排序算法
除利用ORDERBY子句中包括的列對象外,還利用查詢目標列中的所有列對象進行排序(適用於除BLOB、TEXT類型外的所有的其他類型的排序)
MySQL4.1之後出現的改進算法,減少一次I/O,需要增加緩衝區大小容納更多信息。其具體實現過程如下:
1) 獲取與WHERE子句匹配的元組。這一步需要從存儲讀入數據,引發I/O操作。
2) 對於每一個元組,記錄排序鍵值、行的位置值、查詢所需的列。這一步記錄更多內容,需要更大緩存,內存存儲一條元組的信息的長度比算法一的“對值”大許多,這可能引發排序速度問題(排序對象的長度變長,但是內存有限,所以就需把一次內存排序變爲多次,進而影響排序的速度),爲了控制這個問題,MySQL引入一個參數“max_length_for_sort_data”,如果這一步得到的元組長度大於這個值,則不使用算法二。需要MySQL的使用者特別注意的是,在排序中,如果存在“很高磁盤I/O和很低的CPU利用率”的現象,則需要考慮調整“max_length_for_sort_data”的大小以變更換排序算法。
3) 按照排序的鍵值,對元組(元組是第二步的結果)進行排序。
算法二直接從緩衝區中的排序的元組中獲取有序的列信息等(查詢的目的對象),而不是第二次訪問該表讀取所需的列。相比算法一減少一次I/O。
FileSort優化策略
當無法使用索引列排序時,爲了提高Order By的速度,應該嘗試一下優化:
1、避免使用 “select * ” 。查詢的字段越多導致元組長度總合可能
超過max_length_for_sort_data的設置,導致無法使用單路排序算法,只能用雙路排序算法。
超過sort_buffer_size的設置,超出後會創建tmp文件進行合併,導致多次IO
2、適當增大sort_buffer_size參數的設置
3、適當增大max_length_for_sort_data參數的設置
優化group by語句
默認情況下,MySQL對所有group by col1,col2,...的字段進行排序,若查詢包括group by 但用戶想避免排序結果的消耗,可以指定order by null禁止排序。
提高GROUP BY 語句的效率, 可以通過將不需要的記錄在GROUP BY 之前過濾掉
低效:
SELECT JOB , AVG(SAL)
FROM EMP
GROUP by JOB
HAVING JOB = ‘PRESIDENT'
OR JOB = ‘MANAGER'
高效:
SELECT JOB , AVG(SAL)
FROM EMP
WHERE JOB = ‘PRESIDENT'
OR JOB = ‘MANAGER'
GROUP by JOB
優化嵌套查詢用關聯查詢代替子查詢。使用join優化子查詢(in)
優化or條件
(1) where 語句裏面如果帶有or條件, myisam表能用到索引, innodb不行。
1)myisam表:CREATE TABLE IF NOT EXISTS `a` (
`id` int(1) NOT NULL AUTO_INCREMENT,
`uid` int(11) NOT NULL,
`aNum` char(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `uid` (`uid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
mysql> explain select * from a where id=1 or uid =2;
+----+-------------+-------+-------------+---------------+-------------+---------+------+------+---------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------------+---------------+-------------+---------+------+------+---------------------------------------+
| 1 | SIMPLE | a | index_merge | PRIMARY,uid | PRIMARY,uid | 4,4 | NULL | 2 | Using union(PRIMARY,uid); Using where |
+----+-------------+-------+-------------+---------------+-------------+---------+------+------+---------------------------------------+
1 row in set (0.00 sec)
2)innodb表:
CREATE TABLE IF NOT EXISTS `a` (`id` int(1) NOT NULL AUTO_INCREMENT,
`uid` int(11) NOT NULL,
`aNum` char(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `uid` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
mysql> explain select * from a where id=1 or uid =2;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | a | ALL | PRIMARY,uid | NULL | NULL | NULL | 5 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
(2) 含有or的查詢子句,如果要利用索引,則or之間的每個條件列都必須用到索引,若沒有索引,則應考慮增加索引。
(3) 用union替換or:
注意, 以上規則只針對多個索引列有效. 如果有column沒有被索引, 查詢效率可能會因爲你沒有選擇OR而降低.
在下面的例子中, LOC_ID 和REGION上都建有索引.
高效:
- select loc_id , loc_desc , region from location where loc_id = 10
- union
- select loc_id , loc_desc , region from location where region = "melbourne"
select loc_id , loc desc , region from location where loc_id = 10 or region = "melbourne"
如果你堅持要用OR, 那就需要返回記錄最少的索引列寫在最前面.
實際執行效果還需檢驗:
低效:
select…. from location where loc_id = 10 or loc_id = 20 or loc_id = 30
高效
select… from location where loc_in in (10,20,30);
優化分頁查詢
一般分頁查詢
一般的分頁查詢使用簡單的 limit 子句就可以實現。limit 子句聲明如下:
SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset
LIMIT 子句可以被用於指定 SELECT 語句返回的記錄數。需注意以下幾點:
第一個參數指定第一個返回記錄行的偏移量
第二個參數指定返回記錄行的最大數目
如果只給定一個參數:它表示返回最大的記錄行數目
第二個參數爲 -1 表示檢索從某一個偏移量到記錄集的結束所有的記錄行
初始記錄行的偏移量是 0(而不是 1)
下面是一個應用實例:
select * from orders_history where type=8 limit 1000,10;
該條語句將會從表 orders_history 中查詢第1000條數據之後的10條數據,也就是第1001條到第10010條數據。
數據表中的記錄默認使用主鍵(一般爲id)排序,上面的結果相當於:
select * from orders_history where type=8 order by id limit 10000,10;
這種分頁查詢方式會從數據庫第一條記錄開始掃描,越往後,查詢速度越慢,而且查詢的數據越多,也會拖慢總查詢速度。
(1) 使用子查詢優化
這種方式先定位偏移位置的 id,然後往後查詢,這種方式適用於 id 遞增的情況。
select * from orders_history where type=8 limit 100000,1;
select id from orders_history where type=8 limit 100000,1;
select * from orders_history where type=8 and
id>=(select id from orders_history where type=8 limit 100000,1)
limit 100;
select * from orders_history where type=8 limit 100000,100;
4條語句的查詢時間如下:
1 2 3 4 | 第1條語句:3674ms 第2條語句:1315ms 第3條語句:1327ms 第4條語句:3710ms |
針對上面的查詢需要注意:
比較第1條語句和第2條語句:使用 select id 代替 select * 速度增加了3倍
比較第2條語句和第3條語句:速度相差幾十毫秒
比較第3條語句和第4條語句:得益於 select id 速度增加,第3條語句查詢速度增加了3倍
這種方式相較於原始一般的查詢方法,將會增快數倍。
(2) 使用 id 限定優化
這種方式假設數據表的id是連續遞增的,則我們根據查詢的頁數和查詢的記錄數可以算出查詢的id的範圍,可以使用 id between and 來查詢:
select * from orders_history where type=2 and id between 1000000 and 1000100 limit 100;
這種查詢方式能夠極大地優化查詢速度,基本能夠在幾十毫秒之內完成。限制是只能使用於明確知道id的情況,不過一般建立表的時候,都會添加基本的id字段,這爲分頁查詢帶來很多便利。還可以有另外一種寫法:
select * from orders_history where id >= 1000001 limit 100;
當然還可以使用 in 的方式來進行查詢,這種方式經常用在多表關聯的時候進行查詢,使用其他表查詢的id集合,來進行查詢:
select * from orders_history where id in (select order_id from trade_2 where goods = 'pen') limit 100;
這種 in 查詢的方式要注意:某些 mysql 版本不支持在 in 子句中使用 limit。
select film_id, description from film order by title limit 50,5;
優化後:
select film_id, description from film a inner join (select film_id from film order by title limit 50,5) b on a.film_id=b.film_id;
能用UNION ALL就不要用UNION
UNION ALL不執行SELECT DISTINCT函數,這樣就會減少很多不必要的資源。
在Join表的時候使用相當類型的列,並將其索引
如果應用程序有很多JOIN 查詢,你應該確認兩個表中Join的字段是被建過索引的。這樣,MySQL內部會啓動爲你優化Join的SQL語句的機制。
而且,這些被用來Join的字段,應該是相同的類型的。例如:如果你要把 DECIMAL 字段和一個 INT 字段Join在一起,MySQL就無法使用它們的索引。對於那些STRING類型,還需要有相同的字符集才行。(兩個表的字符集有可能不一樣)
能用DISTINCT的就不用GROUP BY
SELECT OrderID FROM Details WHERE UnitPrice > 10 GROUP BY OrderID
可改爲:
SELECT DISTINCT OrderID FROM Details WHERE UnitPrice > 10
使用 varchar/nvarchar 代替 char/nchar
儘可能的使用 varchar/nvarchar 代替 char/nchar ,因爲首先變長字段存儲空間小,可以節省存儲空間,其次對於查詢來說,在一個相對較小的字段內搜索效率顯然要高些。