MySQL系列(六)--索引優化


  在進行數據庫查詢的時候,索引是非常重要的,當然前提是達到一定的數據量。索引就像字典一樣,通過偏旁部首來快速定位,而不是一頁頁

的慢慢找。

  索引依賴存儲引擎層實現,所以支持的索引類型和存儲引擎相關,同一種索引底層實現在不同存儲引擎也是不一樣的

  本文基於MySQL8.0版本,關於explain用法,可以參考:MySQL高級 之 explain執行計划詳解

創建索引語法:

  CREATE TABLE table_name[col_name data_type]

  [UNIQUE|FULLTEXT|SPATIAL]

  [INDEX|KEY]

  [index_name](col_name[length])

  [ASC|DESC]

參數:

  1、UNIQUE、FULLTEXT和SPATIAL為可選參數,分別表示唯一索引、全文索引和空間索引

  2、INDEX和KEY為同義詞,二者作用相同,用來指定創建索引

  3、col_name為需要創建索引的字段列,該列必須從數據表中該定義的多個列中選擇

  4、index_name為指定索引的名稱,為可選參數,如果不指定則MySQL默認col_name為索引

  5、length為可選參數,表示索引的長度,只有字符串類型的字段才能指定索引長度,如果數據列很長的話,MySQL不允許把整列作為索引

  6、ASC或DESC指定升序或者降序的索引值存儲

主鍵:主鍵索引是特殊的唯一索引,不能為null,建表時指定主鍵,只能有一個。

唯一索引:索引列的值必須唯一,包含聯合索引,允許為null。

一般索引:最普通的索引,沒有限制,相對於前兩種效率最差。

全文索引:用於全文搜索,V5.7版本之后,MyIsam和InnoDB支持,只能用於char、varchar、Text列

除了建表時創建索引,還有兩種方式:

1、Alter  TABLE table_name ADD PRIMARY KEY (column_name)/UNIQUE  [indexName] (column_name)/INDEX index_name (column_name)/FULLTEXT (column_name)

2、CREATE UNIQUE INDEX index_name/INDEX index_name ON table_name (column_name)  不適用於primary key和FULLTEXT 

索引分類:

1、B-tree索引:除了archive以外的存儲引擎,都支持

使用B+樹的數據結構實現來存儲數據,能夠加快數據的查詢速度,從索引的根節點開始往下搜索

B-tree索引的數據是順序存儲的,所以適合范圍查找

使用場景:

  1、全值匹配的查詢,例如:id='1001'

  2、匹配最左前綴的查詢,例如現在把id和name建立一個聯合索引,這時候查詢id='1001'可以使用到聯合索引,因為id為這個索引最左字段,

但是如果通過name進行篩選,就無法用到聯合索引

  3、匹配列前綴查詢,例如:id like '100%'也可以用到聯合索引

  4、匹配范圍值的查詢,例如id < '1001' and id > '1010'

  5、精確匹配左前列並范圍匹配另一列

  6、值訪問索引的查詢(覆蓋索引)

總結:對於聯合索引,MySQL從左向右匹配知道遇到范圍查詢(>,<,between,like)就會停止匹配,后面的列就無法用索引

個人使用MySQL8.0版本,測試發現范圍匹配照樣可以使用索引

使用限制:

  1、不使用索引最左列的查詢,無法使用到聯合索引

  2、使用索引時不能跳過索引中的列

  3、not in和<>無法使用索引

  4、索引中有某個列使用了范圍查找,則右邊的所有列都無法使用索引

PS:不僅可以在where查詢中使用,也可以使用在order by和group by中

2、Hash索引:

Memory存儲引擎默認的索引,InnoDB也有Hash索引,這是InnoDB自動建立

Hash索引是基於Hash表實現的,對於Hash索引中所有列,存儲引擎為每一列計算一個hash值,hash索引存儲的就是hash碼

理論上,效率還是好於B樹索引,但是其使用限制太多了

使用限制:

  通過hash索引找到對應的行,然后對行的數據進行讀取,進行兩次查找

  無法用於避免數據的排序操作

  無法使用部分索引鍵的查詢

  只有查詢條件精確匹配Hash索引的所有列,才能使用Hash索引,不能是范圍匹配和模糊匹配

  可能產生hash沖突,不適合選擇性很差的列,例如性別。

選擇性:不重復的索引值和表的記錄數的比值

  比值越高索引的效率越好,因為選擇性高的索引可以在查找時過濾掉更多的行,唯一索引的值是1,這是性能最好的

Innodb也有一個特殊的自適應哈希索引(adaptive hash index)

3、創建自定義哈希索引:

  在B-Tree索引的基礎上創建偽哈希索引,使用B-Tree進行查找,但是 不是使用鍵本身而是hash值進行查找,只需要在where條件中手動指定

hash函數,記住不要使用SHA1()/MD5()

4、空間數據索引(R-Tree):

  MyISAM支持,用於存儲地理數據GPS數據,V5.7之后,InnoDB也支持了。

5、全文索引:

  它是查找文本中的關鍵詞,而不是直接比較索引中的值,全文索引和其它索引的匹配方式完全不同,不適用於where條件操作

6、聚簇索引:不是一種單獨的索引類型,而不是一種數據存儲方式

  數據行存儲在索引的葉子頁,"聚簇"表示數據行和相鄰的鍵值存儲在一起,因為無法把數據行存放在兩個地方,所以一個表只能有一個聚簇索引

  對於Innodb的聚簇索引,Innodb通過主鍵聚集數據,如果沒有定義主鍵,Innodb會選擇一個唯一的非空索引代替,節點頁只包含了索引頁,葉子

頁包含了全部數據,索引列包含了整數值

  對於Innodb來說,主鍵使用AUTO_INCREATMENT比UUID要好,因為Innodb更適合按照主鍵順序插入數據,高並發之下,自增長可能導致鎖競爭

,可以重新設計表,或者使用innodb_autoinc_lock_mode參數配置,如果MySQL版本不支持,可以進行升級

索引的優點:

  大大減少存儲引擎要掃描的數據量

  索引可以幫助我們進行排序以避免使用臨時表,B-tree索引不需要進行數據排序

  索引可以把隨機I/O變成順序I/O

索引帶來的消耗:

  增加寫操作的成本,在對數據數據進行修改的時候,需要更新索引,所以索引越多,寫入的越慢。所以,InnoDB有一層插入緩存,將多次寫入

合並為一次寫入

  增加查詢優化器的選擇時間,同一個查詢如果有很多索引可以選擇,會導致查詢優化器選擇的時間變長

無法使用的場景:

  內存無法使用,只有使用鍵值的索引才能使用

  查詢使用太多列的查詢,如果索引中的數據太大了,也沒啥使用必要

  使用%%的like查詢

PS:過多的索引對寫、讀的效率都是有影響的

索引優化:

1、索引列不能使用表達式或函數

例如:

  where id +1 = 5;

  SELECT * FROM temp WHERE TO_DAYS(date1)-TO_DAYS(current_date) > 30

優化:

  where date1 > date_add(current_date,interval 30 day)

2、前綴索引和索引列的選擇性

  create index index_name on table(col_name(n))

  索引很長的字符列(很長的varchar、text、blob),必須使用前綴索引(MyISAM 727字節,Innodb 1000字節),因為MySQL不允許索引這些

列的完整長度,使用前面所說的偽哈希索引是不行的,通常是索引開始的部分字符,可以節省索引空間,提高索引效率,但是會降低索引的選擇性

所以需要在前綴索引的大小和選擇性之間找到平衡

3、聯合索引:很多列都建立索引不如建立聯合索引

在多個單獨列建立獨立索引大多數情況不能提高MySQL查詢性能,因為需要更多地內存和磁盤IO

  1、當服務器對多個索引做相交操作(多個and)的時候,通常需要一個包含多個列的多列索引,而不是多個獨立的單獨索引

  2、多個or(聯合操作),會消耗大量CPU和內存資源在算法的緩存、排序和合並操作上,這種情況下,還可能有查詢的並發性,還不如沒有

索引,使用union

  3、如果在explain中看到索引合並,就要檢查一下查詢和表結構。可以通過optimizer_switch來關閉索引合並功能,也可以使用ignore index

來讓優化器忽略掉某些索引

如何選擇索引列的順序:在不考慮排序和分組的情況下

  1、經常使用的列放在最左邊,因為索引列是按照從左到右去使用的

  2、選擇性高的列優先

  3、寬度小,意味着每一頁的數據更多,磁盤IO消耗更少

4、覆蓋索引:

  如果一個索引包含、覆蓋所需要查詢的列的值,就稱為"覆蓋索引"

優點:

  可以優化緩存,減少磁盤IO操作

  因為B-tree索引可以減少隨機IO,變隨機IO訪問為順序IO操作,有利於數據的查詢速度

  避免InnoDB索引的二次查詢,這點和Hash索引不同

  避免MyISAM表進行系統調用,因為MySQL只是緩存索引的信息,數據要依賴操作系統緩存,所以訪問數據的時候,需要進行一次系統調用,而

系統調用的性能通常不好

覆蓋索引的限制:

  存在存儲引擎不支持覆蓋索引,Memory

  hash、全文、空間索引都不能做覆蓋索引,只能使用B-Tree索引做覆蓋索引

  查詢中使用太多的列,只有索引的大小遠遠小於數據本身才能發揮索引的作用

  使用了like '%****%'這種雙百分號的查詢,因為存儲引擎底層的API限制的,只能提取數據行的值並加載內存中,然后在內存中進行where過濾

例如:

CREATE TABLE `house_detail` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `description` varchar(255) DEFAULT NULL COMMENT '詳細描述',
  `layout_desc` varchar(255) DEFAULT NULL COMMENT '戶型介紹',
  `traffic` varchar(255) DEFAULT NULL COMMENT '交通出行',
  `round_service` varchar(255) DEFAULT NULL COMMENT '周邊配套',
  `rent_way` int(2) NOT NULL COMMENT '租賃方式',
  `address` varchar(32) NOT NULL COMMENT '詳細地址 ',
  `subway_line_id` bigint(11) DEFAULT NULL,
  `subway_line_name` varchar(32) DEFAULT NULL COMMENT '附近地鐵線名稱',
  `subway_station_id` bigint(11) DEFAULT NULL,
  `subway_station_name` varchar(32) DEFAULT NULL COMMENT '地鐵站名',
  `house_id` bigint(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_on_house_id` (`house_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci 

查詢如下:EXPLAIN SELECT house_id FROM house_detail WHERE house_id = 15;

可以在Extra中看到Using index,還有使用的索引名稱

如果是EXPLAIN SELECT house_id FROM house_detail WHERE house_id LIKE '%1%';

我們看到Using where和Using index

PS:版本越高可能存在更多的內部優化

使用索引來優化查詢:

1、使用索引掃描來優化排序

MySQL實現排序的方式:通過排序操作/按照索引順序掃描數據

索引優化排序的要求:

  1).索引的列順序與order by子句的順序一致

  2).索引列的方向(升序、降序)和order by子句完全一致

  3).多個表的關聯查詢中,order by中的字段全部在關聯表的第一張表中

栗子一:

CREATE TABLE `rental` (
  `rental_id` int(11) NOT NULL AUTO_INCREMENT,
  `rental_date` datetime NOT NULL,
  `inventory_id` mediumint(8) unsigned NOT NULL,
  `customer_id` smallint(5) unsigned NOT NULL,
  `return_date` datetime DEFAULT NULL,
  `staff_id` tinyint(3) unsigned NOT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`rental_id`),
  UNIQUE KEY `rental_date` (`rental_date`,`inventory_id`,`customer_id`),
  KEY `idx_fk_inventory_id` (`inventory_id`),
  KEY `idx_fk_customer_id` (`customer_id`),
  KEY `idx_fk_staff_id` (`staff_id`)
) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

EXPLAIN SELECT * FROM rental WHERE rental_date > '2005-01-01' ORDER BY rental_id;

栗子二:

EXPLAIN SELECT * FROM rental WHERE rental_date = '2005-01-01' ORDER BY inventory_id, customer_id;

EXPLAIN SELECT * FROM rental WHERE rental_date = '2005-01-01' ORDER BY inventory_id DESC, customer_id;

 

 

我們看到Using filesort,證明了第二條要求

栗子三:

EXPLAIN SELECT * FROM rental WHERE rental_date > '2005-01-01' ORDER BY inventory_id DESC, customer_id;

證明了:聯合索引前面的列使用了范圍查找,后面的列不能使用索引,所以使用的是filesort

2、使用hash索引來優化查詢

  前面有簡單提過自定義Hash索引,B-tree索引的長度是有限制的,在列的長度過大的時候,只能使用前綴索引,但是會造成選擇性變差,所以

在B-tree索引上建立Hash索引是一個解決方案

CREATE TABLE `rental` (
  `rental_id` int(11) NOT NULL AUTO_INCREMENT,
  `rental_date` datetime NOT NULL,
  `inventory_id` mediumint(8) unsigned NOT NULL,
  `customer_id` smallint(5) unsigned NOT NULL,
  `return_date` datetime DEFAULT NULL,
  `staff_id` tinyint(3) unsigned NOT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `description` varchar(255) NOT NULL,
  `description_md5` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`rental_id`),
  UNIQUE KEY `rental_date` (`rental_date`,`inventory_id`,`customer_id`),
  KEY `idx_fk_inventory_id` (`inventory_id`),
  KEY `idx_fk_customer_id` (`customer_id`),
  KEY `idx_fk_staff_id` (`staff_id`),
  KEY `idx_md5` (`description_md5`),
  KEY `idx_description` (`description`)
) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

EXPLAIN SELECT * FROM rental WHERE description_md5 = md5('this is food') AND description = 'this is food';

注意hash函數的選擇,盡量不使用SHA1()/md5,這只是舉例,可以選擇CRC32()等其他函數。

原字段和hash處理的字段都要篩選,為了避免hash沖突對性能的影響

特點:

  1、只能匹配鍵值的全職匹配的查詢

  2、索引大小取決於hash函數

3、利用索引來優化鎖

Innodb使用的是行級鎖,只有在修改行的時候,才會被加鎖,索引就減少了鎖定的行數,也加快了鎖的釋放

索引可以加快處理速度,也加快了鎖的釋放

如果沒有索引,對表進行操作,使用排它鎖,就會把整個表鎖住,而使用了索引,只會鎖住一行

CREATE TABLE actor(
	actor_id SMALLINT(5) UNSIGNED NOT NULL auto_increment,
	first_name VARCHAR(45) NOT NULL,
	last_name VARCHAR(45) NOT NULL,
	last_update timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
	PRIMARY KEY (actor_id)
)	ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
EXPLAIN SELECT * FROM actor WHERE last_name='tom' for UPDATE;
--在另一個會話中,執行下個語句,發現無法執行
EXPLAIN SELECT * FROM actor WHERE last_name='jerry' for UPDATE;

--如果把last_name列加上索引
CREATE INDEX idx_lastname ON actor(last_name);
EXPLAIN SELECT * FROM actor WHERE last_name='tom' for UPDATE;
--在另一個會話中,執行下個語句,發現就可以執行
EXPLAIN SELECT * FROM actor WHERE last_name='jerry' for UPDATE;

4、索引的維護和優化

1).刪除重復索引

  例如:id 主鍵primary key 主鍵索引unique key 單列索引index,這時候主鍵實際上就是一個非空的唯一索引

2).刪除冗余索引:已經創建了索引,但是有聯合索引包含了該列

  例如:index(a) index(a,b)

  primary key(id) index(b,id)

可以通過工具pt-duplicate-key-checker h=127.0.0.1查看冗余索引是否有存在的必要,可以saklia官網下載

3).刪除很少或不會再被使用的索引

有SQL語句可以查看數據庫的各個表的索引使用次數,這樣就可以確定有些索引有沒有存在的必要

SELECT
	object_schema,
	object_name,
	index_name,
	b.table_rows
FROM
	performance_schema.table_io_waits_summary_by_index_usage a
JOIN information_schema.TABLES b ON a.OBJECT_SCHEMA = b.table_schema
AND a.OBJECT_NAME = b.table_name
WHERE
	INDEX_NAME IS NOT NULL
AND COUNT_STAR = 0
ORDER BY
	OBJECT_SCHEMA,
	OBJECT_NAME;

 

4).更新索引的統計信息及減少索引碎片

重新生成索引的統計信息:analyze table table_name

MyISAM需要把統計信息保存在磁盤中,需要掃描所有的索引,會把表進行鎖定

InnoDB是保存在內存中,生成的是估算值,不是百分百准確

通過optimize table table_name對表和索引進行維護,但是會鎖表


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM