有兩種藥包治百病:時間和沉默。mysql
MySQL(讀做/maɪ ˈsiːkwəl/「My Sequel」)是一個開放源碼的關係數據庫管理系統,原開發者爲瑞典的MySQL AB公司,目前爲Oracle旗下產品。程序員
被甲骨文公司收購後,自由軟件社羣們對於Oracle是否還會持續支持MySQL社羣版(MySQL之中惟一的免費版本)有所隱憂,所以MySQL的創始人麥克爾·維德紐斯以MySQL爲基礎,成立分支計劃MariaDB。原先一些使用MySQL的開源軟件,部分轉向了MariaDB或其它的數據庫。sql
不能否認的是,MySQL因爲其性能高、成本低、可靠性好,已經成爲最流行的開源數據庫之一,隨着MySQL的不斷成熟,它也逐漸用於更多大規模網站和應用,很是流行的開源軟件組合LAMP中的「M」指的就是MySQL。數據庫
在衆多開源免費的關係型數據庫系統中,MySQL有如下比較出衆的優點:緩存
對於其中運行速度,根據官方介紹,MySQL 8.0 比以前普遍使用的版本 MySQL 5.7 有了兩倍的提高。數據結構
在其官方的Benchmarks中,只讀的性能超過了每秒一百萬次:
讀寫的性能接近每秒二十五萬次:
併發
Why Index 性能
從概念上講,數據庫是數據表的集合,數據表是數據行和數據列的集合。當你執行一個SELECT語句
從數據表中查詢部分數據行的時候,獲得的就是另一個數據表和數據行的集合。優化
固然,咱們都但願得到這個新的集合的時間儘量地短,效率儘量地高,這就是優化查詢。網站
提高查詢速度的技術有不少,其中最重要的就是索引。當你發現本身的查詢速度慢的時候,最快解決問題的方法就是使用索引。索引的使用是影響查詢速度的重要因素。在使用索引以前其餘的優化查詢的動做純粹是浪費時間,只有合理地使用索引以後,纔有必要考慮其餘優化方式。
首先,在你的MySQL上建立t_user_action_log
表,方便下面進行演示。
CREATE DATABASE `ijiangtao_local_db_mysql` /*!40100 DEFAULT CHARACTER SET utf8 */; USE ijiangtao_local_db_mysql; DROP TABLE IF EXISTS t_user_action_log; CREATE TABLE `t_user_action_log` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id', `name` VARCHAR(32) DEFAULT NULL COMMENT '用戶名', `ip_address` VARCHAR(50) DEFAULT NULL COMMENT 'IP地址', `action` INT4 DEFAULT NULL COMMENT '操做:1-登陸,2-登出,3-購物,4-退貨,5-瀏覽', `create_time` TIMESTAMP COMMENT '建立時間', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 1, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.1', 2, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 1, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.3', 1, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 2, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.4', 1, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 2, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.1', 1, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 2, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 1, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 3, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 5, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 2, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 2, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 3, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 3, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 5, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 3, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 3, CURRENT_TIMESTAMP); INSERT INTO t_user_action_log (name, ip_address, `action`, create_time) values ('LiSi', '8.8.8.2', 4, CURRENT_TIMESTAMP);
假如咱們要篩選 action
爲2
的全部記錄,SQL以下:
SELECT id, name, ip_address FROM t_user_action_log WHERE `action`=2;
經過查詢分析器explain
分析這條查詢語句:
EXPLAIN SELECT id, name, ip_address FROM t_user_action_log WHERE `action`=2;
分析結果以下:
其中type
爲ALL
表示要進行全表掃描。這樣效率無疑是極慢的。
下面爲action
列添加索引:
ALTER TABLE t_user_action_log ADD INDEX (`action`);
而後再次執行查詢分析,結果以下:
那麼爲何索引會提升查詢速度呢?緣由是索引會根據索引值進行分類,這樣就不用再進行全表掃描了。咱們看到此次查詢就使用索引了。加索引前Extra
的值是Using Where,加索引後Extra
的值爲空。
好比上圖,action
值爲2
的索引值分類存儲在了索引空間,能夠快速地查詢到索引值所對應的列。
下面介紹一下如何使用SQL建立、查看和刪除索引。
三種方式:
使用CREATE INDEX
建立,語法以下:
CREATE INDEX indexName ON tableName (columnName(length));
例如咱們對ip_address
這一列建立一個長度爲16的索引:
CREATE INDEX index_ip_addr ON t_user_action_log (ip_address(16));
使用ALTER
語句建立,語法以下:
ALTER TABLE tableName ADD INDEX indexName(columnName);
ALTER
語句建立索引前面已經有例子了。下面提供一個設置索引長度的例子:
ALTER TABLE t_user_action_log ADD INDEX ip_address_idx (ip_address(16)); SHOW INDEX FROM t_user_action_log;
建表的時候建立索引:
CREATE TABLE tableName(d INT NOT NULL, columnName columnType, INDEX [indexName] (columnName(length)) );
能夠經過show
語句查看索引:
SHOW INDEX FROM t_user_action_log;
使用ALTER
命令能夠刪除索引,例如:
ALTER TABLE t_user_action_log DROP INDEX index_ip_addr;
索引因爲其提供的優越的查詢性能,彷佛不使用索引就是一個愚蠢的行爲了。可是使用索引,是要付出時間和空間的代價的。所以,索引雖好不可貪多。
下面介紹幾個索引的使用技巧和原則,在使用索引以前,你應該對它們有充分的認識。
索引在提升查詢速度的同時,也因爲須要更新索引而帶來了下降插入、刪除和更新帶索引列的速度的問題。一張數據表的索引越多,在寫操做的時候性能降低的越厲害。
與沒有加索引比較,加索引會更快地使你的磁盤接近使用空間極限。
爲查詢條件、分組、鏈接條件的列加索引,而不是爲查詢輸出結果的列加索引。
例以下面的查詢語句:
select ip_address from t_user_action_log where name='LiSi' group by action order by create_time;
因此能夠考慮增長在 name
action
create_time
列上,而不是 ip_address
。
例如action
列的值包含:一、二、三、四、5,那麼該列的維度就是5。
維度越高(理論上維度的最大值就是數據行的總數),數據列包含的獨一無二的值就越多,索引的使用效果越好。
對於維度很低的數據列,索引幾乎不會起做用,所以沒有必要加索引。
例如性別列的值只有男和女,每種查詢結果佔比大約50%。通常當查詢優化處理器發現查詢結果超過全表的30%的時候,就會跳過索引,直接進行全表掃描。
對短小的值加索引,意味着索引所佔的空間更小,能夠減小I/O活動,同時比較索引的速度也更快。
尤爲是主鍵,要儘量短小。
另外,InnoDB使用的是彙集索引(clustered index),也就是把主鍵和數據行保存在一塊兒。主鍵以外的其餘索引都是二級索引,這些二級索引也保留着一份主鍵,這樣在查詢到索引之後,就能夠根據主鍵找到對應的數據行。若是主鍵太長的話,會形成二級索引佔用的空間變大。
好比下面的action索引保存了對應行的id。
前邊已經講太短小索引的種種好處了,有時候一個字符串的前幾個字符就能惟一標識這條記錄,這個時候設置索引的長度就是很是划算的作法。
前面已經提供了設置索引length
的例子,這裏就不舉例子了。
建立複合索引的語法以下:
CREATE INDEX indexName ON tableName (column1 DESC, column2 DESC, column3 ASC);
咱們能夠看到,最左側的column1索引老是有效的。
對於InnoDB來講,索引可讓查詢鎖住更少的行,從而能夠在併發狀況下擁有更佳表現。
下面演示一下查詢鎖與索引之間的關係。
前面使用的t_user_action_log
表目前有一個id
爲主鍵,還有一個二級索引action
。
下面這條語句的修改範圍是id
值爲1
2
3
4
所在的行,查詢鎖會鎖住id
值爲1
2
3
4
5
所在的行。
update ijiangtao_local_db_mysql.t_user_action_log set name='c1' where id<5;
set autocommit=0; begin; update ijiangtao_local_db_mysql.t_user_action_log set name='c1' where id<5;
-- 沒有被鎖 update ijiangtao_local_db_mysql.t_user_action_log set name='c2' where id=6; -- 被鎖 update ijiangtao_local_db_mysql.t_user_action_log set name='c2' where id=5;
你會發現id=5
的數據行已經被鎖定,id=6
的數據行能夠正常提交。
id=1
和id=5
的數據行能夠update成功了。-- 在鏈接1提交事務 commit;
ip_address
沒有索引的話,會鎖定全表。
鏈接1開啓事務之後commit;
以前,鏈接2對該表的update所有須要等待鏈接1釋放鎖。
set autocommit=0; begin; update ijiangtao_local_db_mysql.t_user_action_log set name='c1' where ip_address='8.8.8.1';
若是索引包含知足查詢的全部數據,就被稱爲覆蓋索引(Covering Indexes),覆蓋索引很是強大,能夠大大提升查詢性能。
覆蓋索引高性能的緣由是:
ijiangtao_local_db_mysql
表的action
列包含索引。使用explain
分析下面的查詢語句,對於索引覆蓋查詢(index-covered query),分析結果Extra
的值是Using index
,表示使用了覆蓋索引 :
explain select `action` from ijiangtao_local_db_mysql.t_user_action_log;
聚簇索引(Clustered Indexes)保證關鍵字的值相近的元組存儲的物理位置也相同,且一個表只能有一個聚簇索引。
字符串類型不建議使用聚簇索引,特別是隨機字符串,由於它們會使系統進行大量的移動操做。
並非全部的存儲引擎都支持聚簇索引,目前InnoDB支持。
若是使用聚簇索引,最好使用AUTO_INCREMENT
列做爲主鍵,應該儘可能避免使用隨機的聚簇主鍵。
從物理位置上看,聚簇索引表比非聚簇的索引表,有更好的訪問性能。
從數據結構角度來看,MySQL支持的索引類型有B樹索引、Hash索引等。
B樹索引對於<、<=、 =、 >=、 >、 <>、!=、 between查詢,進行精確比較操做和範圍比較操做都有比較高的效率。
B樹索引也是InnoDB存儲引擎默認的索引結構。
Hash索引僅能知足=、<=>、in查詢。
Hash索引檢索效率很是高,索引的檢索能夠一次定位,不像B樹索引須要從根節點到枝節點,最後才能訪問到頁節點這樣屢次的I/O訪問,因此Hash索引的查詢效率要遠高於B樹索引。但Hash索引不能使用範圍查詢。
下面提供幾個查詢優化的建議。
前面已經演示過如何使用explain
命令分析查詢語句了,這裏再解釋一下其中幾個有參考價值的字段的含義:
select_type表示查詢中每一個select子句的類型,通常有下面幾個值:
type表示MySQL在表中找到所需行的方式,又稱「訪問類型」,經常使用的類型有:
ALL, index, range, ref, eq_ref, const, system, NULL。
從左到右,性能從差到好。
key列顯示MySQL實際決定使用的鍵(索引),若是沒有選擇索引,鍵是NULL。
possible_keys指出MySQL能使用哪一個索引在表中找到記錄,查詢涉及到的字段上若是存在索引則該索引將被列出,但不必定被查詢使用。
ref表示上述表的鏈接匹配條件,即哪些列或常量被用於查找索引列上的值。
rows
rows表示MySQL根據表統計信息,以及索引選用的狀況,找到所需記錄須要讀取的行數。這個行數是估算的值,實際行數可能不一樣。
用好explain
命令是查詢優化的第一步 !
當數據列被聲明爲NOT NULL之後,在查詢的時候就不須要判斷是否爲NULL,因爲減小了判斷,能夠下降複雜性,提升查詢速度。
若是要表示數據列爲空,可使用0等代替。
MySQL對數值類型的處理速度要遠遠快於字符串,並且數值類型每每更加節省空間。
例如對於「Male」和「Female」能夠用「0」和「1」進行代替。
若是你的數據列的取值是肯定有限的,可使用ENUM類型代替字符串。由於MySQL會把這些值表示爲一系列對應的數字,這樣處理的速度會提升不少。
CREATE TABLE shirts ( name VARCHAR(40), size ENUM('x-small', 'small', 'medium', 'large', 'x-large') ); INSERT INTO shirts (name, size) VALUES ('dress shirt','large'), ('t-shirt','medium'), ('polo shirt','small'); SELECT name, size FROM shirts WHERE size = 'medium';
索引是一個單獨的,存儲在磁盤上的數據結構,索引對數據表中一列或者多列值進行排序,索引包含着對數據表中全部數據的引用指針。
本教程從MySQL開始講起,又介紹了MySQL中索引的使用,最後提供了使用索引的幾條原則和優化查詢的幾個方法。
不管你是DBA仍是軟件開發,菜鳥程序員仍是資深工程師,相信本節提到的關於索引的知識,對你都會有所幫助。