首頁  >  文章  >  資料庫  >  Mysql使用索引實作查詢最佳化_MySQL

Mysql使用索引實作查詢最佳化_MySQL

WBOY
WBOY原創
2016-09-09 08:13:45948瀏覽

索引的目的在於提高查詢效率,可以類比字典,如果要查「mysql」這個單詞,我們肯定需要定位到m字母,然後從下往下找到y字母,再找到剩下的sql。如果沒有索引,那麼你可能需要把所有單字看一遍才能找到你想要的。

1.索引的優點

假設你擁有三個未索引的表t1、t2和t3,每個表都分別包含資料列i1、i2和i3,並且每個表都包含了1000條資料行,其序號從1到1000。尋找某些值相符的資料行組合的查詢可能如下所示:

SELECT t1.i1, t2.i2, t3.i3
FROM t1, t2, t3
WHERE t1.i1 = t2.i2 AND t2.i1 = t3.i3;

  這個查詢的結果應該是1000行,每個資料行包含三個相等的值。如果在沒有索引的情況下處理這個查詢,那麼如果我們不對這些表進行全部地掃描,我們是沒有辦法知道哪些資料行含有哪些值的。因此你必須嘗試所有的組合來找出符合WHERE條件的記錄。可能的組合的數量是1000 x 1000 x 1000(10億!),它是匹配記錄的數量的一百萬倍。這就浪費了大量的工作。這個例子顯示,如果沒有使用索引,隨著表的記錄不斷增長,處理這些表的聯結所花費的時間增長得更快,導致效能很差。我們可以透過索引這些資料表來顯著地提高速度,因為索引讓查詢採用如下所示的方式來處理:

  1.選擇表t1中的第一行並查看該資料行的值。

  2.使用表t2上的索引,直接定位到與t1的值相符的資料行。類似地,使用表t3上的索引,直接定位到與表t2的值相符的資料行。

  3.處理表t1的下一行並重複前面的過程。執行這樣的操作直到t1中的所有資料行都被檢查過。

  在這種情況下,我們仍然對錶t1執行了完整的掃描,但是我們可以在t2和t3上執行索引查找,從這些表中直接地獲取資料行。理論上採用這種方式運行上面的查詢會快一百萬倍。當然這個例子是為了得出結論來人為建立的。然而,它解決的問題是現實的,為沒有索引的表添加索引通常會獲得驚人的效能提高。
-

2.索引的代價

首先,索引加快了檢索的速度,但是減慢了插入和刪除的速度,同時也減慢了更新被索引的資料列中的值的速度。也就是說,索引減慢了大多數涉及寫入操作的速度。發生這種現象的原因在於寫入一筆記錄的時候不但需要寫入資料行,還需要改變所有的索引。資料表帶有的索引越多,需要做出的修改就越多,平均效能的降低程度就越大。在本文的」高效率載入資料」部分中,我們將更細緻地了解這些現象並找出處理方法。

  其次,索引會花費磁碟空間,多個索引相應地花費更多的磁碟空間。這可能導致更快到達資料表的大小限制:

  · 對於MyISAM表,頻繁地索引可能引起索引檔案比資料檔案更快達到最大限制。

  · 對於BDB表,它把資料和索引值一起儲存在同一個檔案中,而添加索引會造成這種表更快達到最大檔案限制。

  · 在InnoDB的共享表空間中分配的所有表都競爭使用相同的公共空間池,因此添加索引會更快地耗盡表空間中的儲存。但是,與MyISAM和BDB表使用的檔案不同,InnoDB共享表空間並不受作業系統的檔案大小限制,因為我們可以把它配置成使用多個檔案。只要有額外的磁碟空間,你就可以透過新增元件來擴展表空間。

  使用單獨表空間的InnoDB表與BDB表受到的限制是一樣的,因為它的資料和索引值都儲存在單一檔案中。

  這些要素的實際意義是:如果你不需要使用特殊的索引幫助查詢執行得更快,就不要建立索引。

3.選擇索引

  假設你已經知道了建立索引的語法,但是語法不會告訴你資料表應該如何索引。這要求我們考慮數據表的使用方式。這一部分指導你如何識別出用於索引的備選資料列,以及如何最好地建立索引:

  用於搜尋、排序和分組的索引資料列並不僅僅是用於輸出顯示的。換句話說,用於索引的最好的替代資料列是那些出現在WHERE子句、join子句、ORDER BY或GROUP BY子句中的資料列。僅僅出現在SELECT關鍵字後面的輸出資料列清單中的資料列不是很好的備選列:

SELECT
col_a <- 不是备选列
FROM
tbl1 LEFT JOIN tbl2
ON tbl1.col_b = tbl2.col_c <- 备选列
WHERE
col_d = expr; <- 备选列

  當然,顯示的資料列與WHERE子句中使用的資料列也可能相同。我們的觀點是輸出清單中的資料列本質上不是用於索引的很好的備選列。

  Join子句或WHERE子句中类似col1 = col2形式的表达式中的数据列都是特别好的索引备选列。前面显示的查询中的col_b和col_c就是这样的例子。如果MySQL能够利用联结列来优化查询,它一定会通过减少整表扫描来大幅度减少潜在的表-行组合。

  考虑数据列的基数(cardinality)。基数是数据列所包含的不同值的数量。例如,某个数据列包含值1、3、7、4、7、3,那么它的基数就是4。索引的基数相对于数据表行数较高(也就是说,列中包含很多不同的值,重复的值很少)的时候,它的工作效果最好。如果某数据列含有很多不同的年龄,索引会很快地分辨数据行。如果某个数据列用于记录性别(只有”M”和”F”两种值),那么索引的用处就不大。如果值出现的几率几乎相等,那么无论搜索哪个值都可能得到一半的数据行。在这些情况下,最好根本不要使用索引,因为查询优化器发现某个值出现在表的数据行中的百分比很高的时候,它一般会忽略索引,进行全表扫描。惯用的百分比界线是”30%”。现在查询优化器更加复杂,把其它一些因素也考虑进去了,因此这个百分比并不是MySQL决定选择使用扫描还是索引的唯一因素。

  索引较短的值。尽可能地使用较小的数据类型。例如,如果MEDIUMINT足够保存你需要存储的值,就不要使用BIGINT数据列。如果你的值不会长于25个字符,就不要使用CHAR(100)。较小的值通过几个方面改善了索引的处理速度:

  · 较短的值可以更快地进行比较,因此索引的查找速度更快了。

  · 较小的值导致较小的索引,需要更少的磁盘I/O。

  · 使用较短的键值的时候,键缓存中的索引块(block)可以保存更多的键值。MySQL可以在内存中一次保持更多的键,在不需要从磁盘读取额外的索引块的情况下,提高键值定位的可能性。

  对于InnoDB和BDB等使用聚簇索引(clustered index)的存储引擎来说,保持主键(primary key)短小的优势更突出。聚簇索引中数据行和主键值存储在一起(聚簇在一起)。其它的索引都是次级索引;它们存储主键值和次级索引值。次级索引屈从主键值,它们被用于定位数据行。这暗示主键值都被复制到每个次级索引中,因此如果主键值很长,每个次级索引就需要更多的额外空间。

  索引字符串值的前缀(prefixe)。如果你需要索引一个字符串数据列,那么最好在任何适当的情况下都应该指定前缀长度。例如,如果有CHAR(200)数据列,如果前面10个或20个字符都不同,就不要索引整个数据列。索引前面10个或20个字符会节省大量的空间,并且可能使你的查询速度更快。通过索引较短的值,你可以获得那些与比较速度和磁盘I/O节省相关的好处。当然你也需要利用常识。仅仅索引某个数据列的第一个字符串可能用处不大,因为如果这样操作,那么在索引中不会有太多的唯一值。

  你可以索引CHAR、VARCHAR、BINARY、VARBINARY、BLOB和TEXT数据列的前缀。

  使用最左(leftmost)前缀。建立多列复合索引的时候,你实际上建立了MySQL可以使用的多个索引。复合索引可以作为多个索引使用,因为索引中最左边的列集合都可以用于匹配数据行。这种列集合被称为”最左前缀”(它与索引某个列的前缀不同,那种索引把某个列的前面几个字符作为索引值)。

  假设你在表的state、city和zip数据列上建立了复合索引。索引中的数据行按照state/city/zip次序排列,因此它们也会自动地按照state/city和state次序排列。这意味着,即使你在查询中只指定了state值,或者指定state和city值,MySQL也可以使用这个索引。因此,这个索引可以被用于搜索如下所示的数据列组合:

state, city, zip
state, city
state

  MySQL不能利用这个索引来搜索没有包含在最左前缀的内容。例如,如果你按照city或zip来搜索,就不会使用到这个索引。如果你搜索给定的state和具体的ZIP代码(索引的1和3列),该索引也是不能用于这种组合值的,尽管MySQL可以利用索引来查找匹配的state从而缩小搜索的范围。

  不要过多地索引。不要认为”索引越多,性能越高”,不要对每个数据列都进行索引。我们在前面提到过,每个额外的索引都会花费更多的磁盘空间,并降低写操作的性能。当你修改表的内容的时候,索引就必须被更新,甚至可能重新整理。如果你的索引很少使用或永不使用,你就没有必要减小表的修改操作的速度。此外,为检索操作生成执行计划的时候,MySQL会考虑索引。建立额外的索引会给查询优化器增加更多的工作量。如果索引太多,有可能(未必)出现MySQL选择最优索引失败的情况。维护自己必须的索引可以帮助查询优化器来避免这类错误。

  如果你考虑给已经索引过的表添加索引,那么就要考虑你将增加的索引是否是已有的多列索引的最左前缀。如果是这样的,不用增加索引,因为已经有了(例如,如果你在state、city和zip上建立了索引,那么没有必要再增加state的索引)。

  让索引类型与你所执行的比较的类型相匹配。在你建立索引的时候,大多数存储引擎会选择它们将使用的索引实现。例如,InnoDB通常使用B树索引。MySQL也使用B树索引,它只在三维数据类型上使用R树索引。但是,MEMORY存储引擎支持散列索引和B树索引,并允许你选择使用哪种索引。为了选择索引类型,需要考虑在索引数据列上将执行的比较操作类型:

  · 对于散列(hash)索引,会在每个数据列值上应用散列函数。生成的结果散列值存储在索引中,并用于执行查询。散列函数实现的算法类似于为不同的输入值生成不同的散列值。使用散列值的好处是散列值比原始值的比较效率更高。散列索引用于执行=或96b4fef55684b9312718d5de63fb7121操作等精确匹配的时候速度非常快。但是对于查询一个值的范围效果就非常差了:

id < 30
weight BETWEEN 100 AND 150

  · B树索引可以用于高效率地执行精确的或者基于范围(使用操作a792d7cec6e729943d26b51f1cfad30a=、>、a8093152e673feb7aba1828c43532094、!=和BETWEEN)的比较。B树索引也可以用于LIKE模式匹配,前提是该模式以文字串而不是通配符开头。

  如果你使用的MEMORY数据表只进行精确值查询,散列索引是很好的选择。这是MEMORY表使用的默认的索引类型,因此你不需要特意指定。如果你希望在MEMORY表上执行基于范围的比较,应该使用B树索引。为了指定这种索引类型,需要给索引定义添加USING BTREE。例如:

CREATE TABLE lookup
(
id INT NOT NULL,
name CHAR(20),
PRIMARY KEY USING BTREE (id)
) ENGINE = MEMORY;

  如果你希望执行的语句的类型允许,单个MEMORY表可以同时拥有散列索引和B树索引,即使在同一个数据列上。

  有些类型的比较不能使用索引。如果你只是通过把值传递到函数(例如STRCMP())中来执行比较操作,那么对它进行索引就没有价值。服务器必须计算出每个数据行的函数值,它会排除数据列上索引的使用。

  使用慢查询(slow-query)日志来识别执行情况较差的查询。这个日志可以帮助你找出从索引中受益的查询。你可以直接查看日志(它是文本文件),或者使用mysqldumpslow工具来统计它的内容。如果某个给定的查询多次出现在”慢查询”日志中,这就是一个线索,某个查询可能没有优化编写。你可以重新编写它,使它运行得更快。你要记住,在评估”慢查询”日志的时候,”慢”是根据实际时间测定的,在负载较大的服务器上”慢查询”日志中出现的查询会多一些。

*4.建索引的几大原则*

4.1.最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、d0d3ef2feb1bc4c8d48c16963c753a7e 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

4.2.=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式

4.3.尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录

4.4.索引列不能參與計算,保持列“乾淨”,例如from_unixtime(create_time) = '2014-05-29'就不能使用到索引,原因很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,需要把所有元素都套用函數才能比較,顯然成本太大。所以語句應該要寫成create_time = unix_timestamp('2014-05-29');

4.5.盡量的擴充索引,不要新建索引。例如表中已經有a的索引,現在要加(a,b)的索引,那就只需要修改原來的索引即可。

以上所述是小編給大家介紹的Mysql使用索引實現查詢優化,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對網站的支持!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn