MySQL 性能調(diào)優(yōu)

MySQL性能調(diào)優(yōu)

索引

索引是什么

官方介紹索引是幫助MySQL高效獲取數(shù)據(jù)的數(shù)據(jù)結構。
筆者理解索引相當于一本書的目錄,通過目錄就知道要的資料在哪里,不用一頁一頁查閱找出需要的資料。

索引目的

索引的目的在于提高查詢效率,可以類比字典,如果要查“mysql”這個單詞,我們肯定需要定位到m字母,然后從下往下找到y(tǒng)字母,再找到剩下的sql。
如果沒有索引,那么你可能需要把所有單詞看一遍才能找到你想要的,如果我想找到m開頭的單詞呢?或者ze開頭的單詞呢?是不是覺得如果沒有索引,這個事情根本無法完成?


索引原理

除了詞典,生活中隨處可見索引的例子,如火車站的車次表、圖書的目錄等。
它們的原理都是一樣的,通過不斷的縮小想要獲得數(shù)據(jù)的范圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是我們總是通過同一種查找方式來鎖定數(shù)據(jù)。
數(shù)據(jù)庫也是一樣,但顯然要復雜許多,因為不僅面臨著等值查詢,還有范圍查詢(>、<、between、in)、模糊查詢(like)、并集查詢(or)等等。
數(shù)據(jù)庫應該選擇怎么樣的方式來應對所有的問題呢?

我們回想字典的例子,能不能把數(shù)據(jù)分成段,然后分段查詢呢?
最簡單的如果1000條數(shù)據(jù),1到100分成第一段,101到200分成第二段,201到300分成第三段……這樣查第250條數(shù)據(jù),只要找第三段就可以了,一下子去除了90%的無效數(shù)據(jù)。
但如果是1千萬的記錄呢,分成幾段比較好?稍有算法基礎的同學會想到搜索樹,其平均復雜度是lgN,具有不錯的查詢性能。
但這里我們忽略了一個關鍵的問題,復雜度模型是基于每次相同的操作成本來考慮的,
數(shù)據(jù)庫實現(xiàn)比較復雜,數(shù)據(jù)保存在磁盤上,而為了提高性能,每次又可以把部分數(shù)據(jù)讀入內(nèi)存來計算,
因為我們知道訪問磁盤的成本大概是訪問內(nèi)存的十萬倍左右,所以簡單的搜索樹難以滿足復雜的應用場景。

磁盤IO與預讀

前面提到了訪問磁盤,那么這里先簡單介紹一下磁盤IO和預讀,
磁盤讀取數(shù)據(jù)靠的是機械運動,每次讀取數(shù)據(jù)花費的時間可以分為尋道時間、旋轉延遲、傳輸時間三個部分,尋道時間指的是磁臂移動到指定磁道所需要的時間,主流磁盤一般在5ms以下;
旋轉延遲就是我們經(jīng)常聽說的磁盤轉速,比如一個磁盤7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms;
傳輸時間指的是從磁盤讀出或?qū)?shù)據(jù)寫入磁盤的時間,一般在零點幾毫秒,相對于前兩個時間可以忽略不計。
那么訪問一次磁盤的時間,即一次磁盤IO的時間約等于5+4.17 = 9ms左右,聽起來還挺不錯的,
但要知道一臺500 -MIPS的機器每秒可以執(zhí)行5億條指令,因為指令依靠的是電的性質(zhì),
換句話說執(zhí)行一次IO的時間可以執(zhí)行40萬條指令,數(shù)據(jù)庫動輒十萬百萬乃至千萬級數(shù)據(jù),每次9毫秒的時間,顯然是個災難。
下圖是計算機硬件延遲的對比圖,供大家參考:

考慮到磁盤IO是非常高昂的操作,計算機操作系統(tǒng)做了一些優(yōu)化,
當一次IO時,不光把當前磁盤地址的數(shù)據(jù),而是把相鄰的數(shù)據(jù)也都讀取到內(nèi)存緩沖區(qū)內(nèi),
因為局部預讀性原理告訴我們,當計算機訪問一個地址的數(shù)據(jù)的時候,與其相鄰的數(shù)據(jù)也會很快被訪問到。
每一次IO讀取的數(shù)據(jù)我們稱之為一頁(page)。
具體一頁有多大數(shù)據(jù)跟操作系統(tǒng)有關,一般為4k或8k,也就是我們讀取一頁內(nèi)的數(shù)據(jù)時候,實際上才發(fā)生了一次IO,這個理論對于索引的數(shù)據(jù)結構設計非常有幫助。

索引的數(shù)據(jù)結構

前面講了生活中索引的例子,索引的基本原理,數(shù)據(jù)庫的復雜性,又講了操作系統(tǒng)的相關知識,目的就是讓大家了解,任何一種數(shù)據(jù)結構都不是憑空產(chǎn)生的,一定會有它的背景和使用場景。
我們現(xiàn)在總結一下,我們需要這種數(shù)據(jù)結構能夠做些什么,其實很簡單,那就是:每次查找數(shù)據(jù)時把磁盤IO次數(shù)控制在一個很小的數(shù)量級,最好是常數(shù)數(shù)量級。那么我們就想到如果一個高度可控的多路搜索樹是否能滿足需求呢?就這樣,b+樹應運而生。


詳解b+樹

如上圖,是一顆b+樹,關于b+樹的定義可以參見B+樹
這里只說一些重點,淺藍色的塊我們稱之為一個磁盤塊,可以看到每個磁盤塊包含幾個數(shù)據(jù)項(深藍色所示)和指針(黃色所示),
如磁盤塊1包含數(shù)據(jù)項17和35,包含指針P1、P2、P3,P1表示小于17的磁盤塊,P2表示在17和35之間的磁盤塊,P3表示大于35的磁盤塊。
真實的數(shù)據(jù)存在于葉子節(jié)點即3、5、9、10、13、15、28、29、36、60、75、79、90、99。
非葉子節(jié)點只不存儲真實的數(shù)據(jù),只存儲指引搜索方向的數(shù)據(jù)項,
如17、35并不真實存在于數(shù)據(jù)表中。

b+樹的查找過程

如圖所示,如果要查找數(shù)據(jù)項29,
那么首先會把磁盤塊1由磁盤加載到內(nèi)存,此時發(fā)生一次IO,
在內(nèi)存中用二分查找確定29在17和35之間,鎖定磁盤塊1的P2指針,內(nèi)存時間因為非常短(相比磁盤的IO)可以忽略不計,
通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內(nèi)存,發(fā)生第二次IO,
29在26和30之間,鎖定磁盤塊3的P2指針,通過指針加載磁盤塊8到內(nèi)存,發(fā)生第三次IO,
同時內(nèi)存中做二分查找找到29,結束查詢,總計三次IO。

真實的情況是,3層的b+樹可以表示上百萬的數(shù)據(jù),如果上百萬的數(shù)據(jù)查找只需要三次IO,性能提高將是巨大的,
如果沒有索引,每個數(shù)據(jù)項都要發(fā)生一次IO,那么總共需要百萬次的IO,顯然成本非常非常高。

b+樹性質(zhì)

通過上面的分析,我們知道IO次數(shù)取決于b+數(shù)的高度h,假設當前數(shù)據(jù)表的數(shù)據(jù)為N,每個磁盤塊的數(shù)據(jù)項的數(shù)量是m,則有h=㏒(m+1)N,當數(shù)據(jù)量N一定的情況下,m越大,h越??;
而m = 磁盤塊的大小 / 數(shù)據(jù)項的大小,磁盤塊的大小也就是一個數(shù)據(jù)頁的大小,是固定的,如果數(shù)據(jù)項占的空間越小,數(shù)據(jù)項的數(shù)量越多,樹的高度越低。
這就是為什么每個數(shù)據(jù)項,即索引字段要盡量的小,比如int占4字節(jié),要比bigint8字節(jié)少一半。
這也是為什么b+樹要求把真實的數(shù)據(jù)放到葉子節(jié)點而不是內(nèi)層節(jié)點,一旦放到內(nèi)層節(jié)點,磁盤塊的數(shù)據(jù)項會大幅度下降,導致樹增高。
當數(shù)據(jù)項等于1時將會退化成線性表。

當b+樹的數(shù)據(jù)項是復合的數(shù)據(jù)結構,比如(name,age,sex)的時候,
b+數(shù)是按照從左到右的順序來建立搜索樹的,
比如當(張三,20,F)這樣的數(shù)據(jù)來檢索的時候,
b+樹會優(yōu)先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,最后得到檢索的數(shù)據(jù);
但當(20,F)這樣的沒有name的數(shù)據(jù)來的時候,b+樹就不知道下一步該查哪個節(jié)點,
因為建立搜索樹的時候name就是第一個比較因子,必須要先根據(jù)name來搜索才能知道下一步去哪里查詢。
比如當(張三,F)這樣的數(shù)據(jù)來檢索時,b+樹可以用name來指定搜索方向,
但下一個字段age的缺失,所以只能把名字等于張三的數(shù)據(jù)都找到,然后再匹配性別是F的數(shù)據(jù)了,
這個是非常重要的性質(zhì),即索引的最左匹配特性。


索引是不是越多越好?

索引能夠極大的提高數(shù)據(jù)檢索效率,也能夠改善排序分組操作的性能,但是我們不能忽略的一個問題就是索引是完全獨立于基礎數(shù)據(jù)之外的一部分數(shù)據(jù)。
假設我們在更新表中有字段的同時,也更新索引數(shù)據(jù),調(diào)整因為更新所帶來鍵值變化后的索引信息。
而如果我們沒有對字段進行索引的話,MySQL 所需要做的僅僅只是更新表中字段 的信息。
這樣,所帶來的最明顯的資源消耗就是增加了更新所帶來的IO量和調(diào)整索引所致的計算量。

此外,索引是需要占用存儲空間的,而且隨著數(shù)據(jù)量的增長,所占用的空間也會不斷增長。
所以索引還會帶來存儲空間資源消耗的增長。


什么場景應該加索引?加索引的四個原則

1. 較頻繁的作為查詢條件的字段應該創(chuàng)建索引

提高數(shù)據(jù)查詢檢索的效率最有效的辦法就是減少需要訪問的數(shù)據(jù)量,從上面所了解到的索引的益處中我們知道了,索引正是我們減少通過索引鍵字段作為查詢條件的Query 的IO 量的最有效手段。所以一般來說我們應該為較為頻繁的查詢條件字段創(chuàng)建索引。

2. 唯一性太差的字段不適合單獨創(chuàng)建索引,即使頻繁作為查詢條件

唯一性太差的字段主要是指哪些呢?
如狀態(tài)字段,類型字段等等,這些字段中存方的數(shù)據(jù)可能總共就是那么幾個幾十個值重復使用,每個值都會存在于成千上萬或是更多的記錄中。
對于這類字段,我們完全沒有必要創(chuàng)建單獨的索引的。
因為即使我們創(chuàng)建了索引,MySQL Query Optimizer 大多數(shù)時候也不會去選擇使用,
如果什么時候MySQL Query Optimizer 抽了一下風選擇了這種索引,那么非常遺憾的告訴你,這可能會帶來極大的性能問題。
由于索引字段中每個值都含有大量的記錄,那么存儲引擎在根據(jù)索引訪問數(shù)據(jù)的時候會帶來大量的隨機IO,甚至有些時候可能還會出現(xiàn)大量的重復IO。

3. 更新非常頻繁的字段不適合創(chuàng)建索引

上面在索引的弊端中我們已經(jīng)分析過了,索引中的字段被更新的時候,不僅僅需要更新表中的數(shù)據(jù),同時還要更新索引數(shù)據(jù),以確保索引信息是準確的。
這個問題所帶來的是IO 訪問量的較大增加,不僅僅影響更新Query 的響應時間,還會影響整個存儲系統(tǒng)的資源消耗,加大整個存儲系統(tǒng)的負載。

4. 不會出現(xiàn)在WHERE子句中的字段不創(chuàng)建索引

查詢時,不會命中索引。那么索引就沒有存在的意義了。

創(chuàng)建索引的舉例說明

CREATE TABLE `v9_member_menu` (
  `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,    # 主鍵標識
  `name` char(40) NOT NULL DEFAULT '',                  # 菜單名稱
  `parentid` smallint(6) NOT NULL DEFAULT '0',          # 父級ID
  `m` char(20) NOT NULL DEFAULT '',                     # 模型名稱
  `c` char(20) NOT NULL DEFAULT '',                     # 控制器名
  `a` char(20) NOT NULL DEFAULT '',                     # 方法名
  `data` char(100) NOT NULL DEFAULT '',                 # 附加數(shù)據(jù)
  `listorder` smallint(6) unsigned NOT NULL DEFAULT '0',# 排序值
  `display` enum('1','0') NOT NULL DEFAULT '1',         # 是否顯示
  `isurl` enum('1','0') NOT NULL DEFAULT '0',           # 是否是一個鏈接
  `url` char(255) NOT NULL DEFAULT '',                  # 鏈接地址
  PRIMARY KEY (`id`),
  KEY `listorder` (`listorder`),     
  KEY `parentid` (`parentid`),
  KEY `module` (`m`,`c`,`a`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

關于 菜單的使用場景, 我做出如下整理

  1. 會根據(jù) url分割出 m,c,a 然后進行查詢菜單ID,再關聯(lián)權限表,查詢是否有權限。
  2. 根據(jù) 菜單ID 獲取菜單信息,例如 刪,改,查的應用場景
  3. 會根據(jù)菜單的父級ID 查詢父級信息, 或者同自己的ID 查詢子級信息。
  4. 顯示菜單時,通常會進行排序。
  • 第一個情況 就符合 ,創(chuàng)建復合索引的條件,在where中經(jīng)常會一起出現(xiàn),
    例如 m=home and c=index and a=login

  • 第二個情況 可以使用主鍵索引,主鍵本身就自帶索引屬性。

  • 第三個情況,在查詢子級時 通常會使用到。

  • 第四個情況: 排序也經(jīng)常使用到。

  • data 和 url 為何不加索引?
    data 和 url 屬于詳細內(nèi)容, 一般只用于展示,不會加入到where條件查詢中,所以不需要加索引。

  • display 和 isurl 為何不加索引
    display 和 isurl 一樣 他的數(shù)值很單一,不是1就是0,沒必要加索引,而且符合條件的數(shù)據(jù)有很多,給mysql帶來大量的隨機IO。


索引的類型

聚簇索引和非聚簇索引

索引分為聚簇索引和非聚簇索引兩種,聚簇索引是按照數(shù)據(jù)存放的物理位置為順序的,而非聚簇索引就不一樣了;
聚簇索引能提高多行檢索的速度,而非聚簇索引對于單行的檢索很快。

聚簇索引是一種數(shù)據(jù)存儲方式,它實際上是在同一個結構中保存了B+樹索引和數(shù)據(jù)行,InnoDB表是按照聚簇索引組織的(類似于Oracle的索引組織表)。
InnoDB通過主鍵聚簇數(shù)據(jù),如果沒有定義主鍵,會選擇一個唯一的非空索引代替,如果沒有這樣的索引,會隱式定義個主鍵作為聚簇索引。

下圖形象說明了聚簇索引表(InnoDB)和非聚簇索引(MyISAM)的區(qū)別:

對于非聚簇索引表來說(右圖),表數(shù)據(jù)和索引是分成存儲的,主鍵索引和二級索引存儲上沒有任何區(qū)別。

而對于聚簇索引表來說(左圖),表數(shù)據(jù)是和主鍵一起存儲的,主鍵索引的葉結點存儲行數(shù)據(jù),二級索引的葉結點存儲行的主鍵值。

聚簇索引表最大限度地提高了I/O密集型應用的性能,但它也有以下幾個限制:

1)插入速度嚴重依賴于插入順序,按照主鍵的順序插入是最快的方式,否則將會出現(xiàn)頁分裂,嚴重影響性能。
因此,對于InnoDB表,我們一般都會定義一個自增的ID列為主鍵。

2)更新主鍵的代價很高,因為將會導致被更新的行移動。因此,對于InnoDB表,我們一般定義主鍵為不可更新。

3)二級索引訪問需要兩次索引查找,第一次找到主鍵值,第二次根據(jù)主鍵值找到行數(shù)據(jù)。
二級索引的葉節(jié)點存儲的是主鍵值,而不是行指針(非聚簇索引存儲的是指針或者說是地址),這是為了減少當出現(xiàn)行移動或數(shù)據(jù)頁分裂時二級索引的維護工作,但會讓二級索引占用更多的空間。

聚簇索引的葉節(jié)點就是數(shù)據(jù)節(jié)點,而非聚簇索引的頁節(jié)點仍然是索引檢點,并保留一個鏈接指向?qū)獢?shù)據(jù)塊。
**聚簇索引主鍵的插入速度要比非聚簇索引主鍵的插入速度慢很多。 **

相比之下,聚簇索引適合排序,非聚簇索引不適合用在排序的場合。
因為聚簇索引本身已經(jīng)是按照物理順序放置的,排序很快。
非聚簇索引則沒有按序存放,需要額外消耗資源來排序。
當你需要取出一定范圍內(nèi)的數(shù)據(jù)時,用聚簇索引也比用非聚簇索引好。


主鍵索引(PRIMARY KEY )

主鍵自帶索引屬性。 不管是 修改查詢刪除 基本都會用到它。


普通索引(Normal)

這是最基本的索引,它沒有任何限制,比如上文中為listorder字段創(chuàng)建的索引就是一個普通索引,MyIASM中默認的BTREE類型的索引,也是我們大多數(shù)情況下用到的索引。

實例

–直接創(chuàng)建索引
CREATE INDEX index_name ON table(column(length))

–修改表結構的方式添加索引
ALTER TABLE table_name ADD INDEX index_name ON (column(length))

–創(chuàng)建表的時候同時創(chuàng)建索引
CREATE TABLE `table` (

    `id` int(11) NOT NULL AUTO_INCREMENT ,
    
    `title` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
    
    `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL ,
    
    `time` int(10) NULL DEFAULT NULL ,
    
    PRIMARY KEY (`id`),
    
    INDEX index_name (title(length))
)

–刪除索引
DROP INDEX index_name ON table


唯一索引(Unique)

與普通索引類似,不同的就是:索引列的值必須唯一,但允許有空值(注意和主鍵不同)。
如果是組合索引,則列值的組合必須唯一,創(chuàng)建方法和普通索引類似。

例如:用戶表的 用戶名 和 郵箱 都可以進行唯一索引

實例
–創(chuàng)建唯一索引
CREATE UNIQUE INDEX indexName ON table(column(length))

–修改表結構
ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))

–創(chuàng)建表的時候直接指定
CREATE TABLE `table` (

    `id` int(11) NOT NULL AUTO_INCREMENT ,
    
    `title` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
    
    `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL ,
    
    `time` int(10) NULL DEFAULT NULL ,
    
    PRIMARY KEY (`id`),
    
    UNIQUE indexName (title(length))

);

全文索引(Full Text)

MySQL從3.23.23版開始支持全文索引和全文檢索,F(xiàn)ULLTEXT索引僅可用于 MyISAM 表;
他們可以從CHAR、VARCHAR或TEXT列中作為CREATE TABLE語句的一部分被創(chuàng)建,
或是隨后使用ALTER TABLE 或CREATE INDEX被添加。

對于較大的數(shù)據(jù)集,將你的資料輸入一個沒有FULLTEXT索引的表中,然后創(chuàng)建索引,其速度比把資料輸入現(xiàn)有FULLTEXT索引的速度更為快。
不過切記對于大容量的數(shù)據(jù)表,生成全文索引是一個非常消耗時間非常消耗硬盤空間的做法。

在數(shù)據(jù)量不是很大的情況下 可以利用 全文索引做 站內(nèi)搜索。
但是得先分詞,才能進行全文檢索。檢索時 是通過 空格來分割詞匯。
最好是 新建一個關聯(lián)表(其中 存儲分詞的字段 用全文索引),把分詞后的內(nèi)容 用 空格分割 存儲到 關聯(lián)表,然后對應原始表。

查詢流程如下

  1. 查詢關聯(lián)表
  2. 獲取所有能查到的 文章ID
  3. 根據(jù)文章ID 獲取文章數(shù)據(jù)

也可以配合第三方的檢索插件 來進行全文檢索
packagist.org 搜索中文分詞

小項目可以使用 結巴分詞


單列索引 和 復合索引

多個單列索引與單個多列索引的查詢效果不同,因為執(zhí)行查詢時,MySQL只能使用一個索引,會從多個索引中選擇一個限制最為嚴格的索引。
即 mysql 底層自己會判斷 使用那個索引 速度會更快


組合索引(最左前綴)

平時用的SQL查詢語句一般都有比較多的限制條件,所以為了進一步榨取MySQL的效率,就要考慮建立組合索引。
例如上表中針對title和time建立一個組合索引:

    ALTER TABLE article ADD INDEX index_titme_time (title(50),time(10))

建立這樣的組合索引,其實是相當于分別建立了下面兩組組合索引:
–title,time
–title

為什么沒有time這樣的組合索引呢?這是因為MySQL組合索引“最左前綴”的結果。
簡單的理解就是只從最左面的開始組合。
并不是只要包含這兩列的查詢都會用到該組合索引,如下面的幾個SQL所示:

–使用到上面的索引
SELECT * FROM article WHREE title='測試' AND time=1234567890;
SELECT * FROM article WHREE utitle='測試';

–不使用上面的索引
SELECT * FROM article WHREE time=1234567890;

自創(chuàng)索引表

如果又需要可以自創(chuàng) 索引表(關聯(lián)表)。

例如 現(xiàn)在有一個文章表, 需要做一個文章的站內(nèi)搜索
那么 我們需要新建一個文章表

CREATE TABLE `article` (
  `id` int(11) unsigned NOT NULL COMMENT '主鍵',
  `title` varchar(255) NOT NULL COMMENT '標題',
  `author` varchar(255) NOT NULL DEFAULT '' COMMENT '作者',
  `content` text NOT NULL COMMENT '內(nèi)容',
  `create_time` int(11) unsigned NOT NULL COMMENT '創(chuàng)建時間',
  `update_time` int(11) unsigned DEFAULT NULL COMMENT '修改時間',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

創(chuàng)建一個 分詞索引表

CREATE TABLE `article_participle` (
  `id` int(11) NOT NULL,
  `article_id` int(11) unsigned NOT NULL COMMENT '文章表ID ',
  `participle` varchar(1000) NOT NULL COMMENT '關鍵詞 以空格分隔',
  PRIMARY KEY (`id`),
  UNIQUE KEY `article_id` (`article_id`) USING BTREE COMMENT '文章ID',
  FULLTEXT KEY `participle` (`participle`) COMMENT '中文分詞存儲'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

先根據(jù) 搜索的關鍵詞 搜索 分詞索引表
然后在根據(jù)搜索出的結果 (article_id 文章ID) 搜索文章表


索引方法

BTree 索引特征

BTree索引可以被用在像=,>,>=,<,<=和BETWEEN這些比較操作符上。而且還可以用于LIKE操作符,只要它的查詢條件是一個不以通配符開頭的常量。像下面的語句就可以使用索引:

SELECT * FROM tbl_name WHERE key_col LIKE 'Patrick%';
SELECT * FROM tbl_name WHERE key_col LIKE 'Pat%_ck%';

下面這兩種情況不會使用索引:

SELECT * FROM tbl_name WHERE key_col LIKE '%Patrick%';
SELECT * FROM tbl_name WHERE key_col LIKE other_col;

第一條是因為它以通配符開頭,第二條是因為沒有使用常量。

假如你使用... LIKE '%string%'而且string超過三個字符,MYSQL使用Turbo Boyer-Moore algorithm算法來初始化查詢表達式,然后用這個表達式來讓查詢更迅速。

一個這樣的查詢col_name IS NULL是可以使用col_name的索引的。

任何一個沒有覆蓋所有WHERE中AND級別條件的索引是不會被使用的。也就是說,要使用一個索引,這個索引中的第一列需要在每個AND組中出現(xiàn)。

下面的WHERE條件會使用索引:

... WHERE index_part1=1 AND index_part2=2 AND other_column=3
    /* index = 1 OR index = 2 */
... WHERE index=1 OR A=10 AND index=2
    /* 優(yōu)化成 "index_part1='hello'" */
... WHERE index_part1='hello' AND index_part3=5
    /* 可以使用 index1 的索引但是不會使用 index2 和 index3 */
... WHERE index1=1 AND index2=2 OR index1=3 AND index3=3;

下面的WHERE條件不會使用索引:

    /* index_part1 沒有被使用到 */
... WHERE index_part2=1 AND index_part3=2

    /* 索引 index 沒有出現(xiàn)在每個 where 子句中 */
... WHERE index=1 OR A=10
    /* 沒有索引覆蓋所有列 */
... WHERE index_part1=1 OR index_part2=10

有時候mysql不會使用索引,即使這個在可用的情況下。
例如當mysql預估使用索引會讀取大部分的行數(shù)據(jù)時。(在這種情況下,一次全表掃描可能比使用索引更快,因為它需要更少的檢索)。
然而,假如語句中使用LIMIT來限定返回的行數(shù),mysql則會使用索引。
因為當結果行數(shù)較少的情況下使用索引的效率會更高。

位圖索引 (HASH)

Hash類型的索引有一些區(qū)別于以上所述的特征:

1.相對于BTree索引,占用的空間非常小,創(chuàng)建和使用非常快。
位圖索引由于只存儲鍵值的起止Rowid和位圖,占用的空間非常少。

2.不適合鍵值較多的列。

3.不適合update、insert、delete頻繁的列。

4.可以存儲null值。
BTree索引由于不記錄空值,當基于is null的查詢時,會使用全表掃描。
而對位圖索引列進行is null查詢時,則可以使用索引。

5.當select count(XX) 時,可以直接訪問索引中一個位圖就快速得出統(tǒng)計數(shù)據(jù)。

6.當根據(jù)鍵值做and,or或 in(x,y,..)查詢時,直接用索引的位圖進行或運算,快速得出結果行數(shù)據(jù)統(tǒng)計。

7.它們只能用于對等比較,例如=和<=>操作符(但是快很多)。它們不能被用于像<這樣的范圍查詢條件。假如系統(tǒng)只需要使用像“鍵值對”的這樣的存儲結構,盡量使用hash類型索引。

8.優(yōu)化器不能用hash索引來為ORDER BY操作符加速。(這類索引不能被用于搜索下一個次序的值)

9.mysql不能判斷出兩個值之間有多少條數(shù)據(jù)(這需要使用范圍查詢操作符來決定使用哪個索引)。假如你將一個MyISAM表轉為一個依靠hash索引的MEMORY表,可能會影響一些語句(的性能)。

10.只有完整的鍵才能被用于搜索一行數(shù)據(jù)。(假如用B-tree索引,任何一個鍵的片段都可以用于查找)。


去索引化

為了更好的提高并發(fā)量,又產(chǎn)生了另一個思想!去索引化。
去索引化,并不是真正的去掉索引。只是通過異步操作把索引 像關系表那樣存起來。
這樣可以提升,高并發(fā)寫入的性能,又可以提升數(shù)據(jù)查詢的性能。


SQL語句優(yōu)化

經(jīng)常用到的需要條件字段 需要建立索引
避免在 where 子句中對字段進行 null 值判斷(全表掃描)
避免 !=或<>操作
避免 in 和 not in 可用(between)
避免 '%c%' 考慮使用全文檢索。
避免使用 參數(shù),子句,函數(shù)操作
避免表達式操作 如 num/2=100; 優(yōu)化后 num=100*2;
查詢時 把條件中 有索引的 放在最左邊 (最左前綴)
exists 代替 in


分布式架構 和集群架構的區(qū)別

簡單說,分布式是以縮短單個任務的執(zhí)行時間來提升效率的,而集群則是通過提高單位時間內(nèi)執(zhí)行的任務數(shù)來提升效率。

例如:
如果一個任務由10個子任務組成,每個子任務單獨執(zhí)行需1小時,則在一臺服務器上執(zhí)行改任務需10小時。

采用分布式方案,提供10臺服務器,每臺服務器只負責處理一個子任務,不考慮子任務間的依賴關系,執(zhí)行完這個任務只需一個小時。(這種工作模式的一個典型代表就是Hadoop的Map/Reduce分布式計算模型)

而采用集群方案,同樣提供10臺服務器,每臺服務器都能獨立處理這個任務。假設有10個任務同時到達,10個服務器將同時工作,10小后,10個任務同時完成,這樣,整身來看,還是1小時內(nèi)完成一個任務!


分表

為什么要分表?

數(shù)據(jù)庫中的數(shù)據(jù)量不一定是可控的,在未進行分庫分表的情況下,隨著時間和業(yè)務的發(fā)展,庫中的表會越來越多,表中的數(shù)據(jù)量也會越來越大,相應地,數(shù)據(jù)操作,增刪改查的開銷也會越來越大;

另外,由于無法進行分布式式部署,而一臺服務器的資源(CPU、磁盤、內(nèi)存、IO等)是有限的,最終數(shù)據(jù)庫所能承載的數(shù)據(jù)量、數(shù)據(jù)處理能力都將遭遇瓶頸。

分表的方式?

水平切分(橫向切分)
垂直切分(縱向切分)
聯(lián)合切分(橫向切分 和縱向切分)


垂直分表

何為垂直分表?

即將表按照功能模塊、關系密切程度劃分出來,部署到不同的數(shù)據(jù)表上。
比如user(用戶表 主要存用戶名 和密碼)表和user_details(用戶詳情 頭像,地址等)表。
比如博客表中的title和content表。(大字段 拆到另外一個表里)

大字段垂直切分

什么樣的字段適合于從表中拆分:
首先要肯定是大字段。為什么?原因很簡單,就是因為他的大。
大字段一般都是存放著一些較長的Detail 信息,如文章的內(nèi)容,帖子的內(nèi)容,產(chǎn)品的介紹等等。

其次是和表中其他字段相比訪問頻率明顯要少很多
如果我們要查詢某些記錄的某幾個字段,數(shù)據(jù)庫并不是只需要訪問我們需要查詢的哪幾個字段,而是需要讀取其他所有字段這樣,我們就不得不讀取包括大字段在內(nèi)的很多并不相干的數(shù)據(jù)。
而由于大字段所占的空間比例非常大,自然所浪費的IO 資源也就非常之大了。

實際上,在有些時候,我們甚至都不一定非要大字段才能進行垂直分拆。
在有些場景下,有的表中大部分字段平時都很少訪問,而其中的某幾個字段卻是訪問頻率非常高。
對于這種表,也非常適合通過垂直分拆來達到優(yōu)化性能的目的。

垂直切分的優(yōu)點

  • 數(shù)據(jù)庫的拆分簡單明了,拆分規(guī)則明
  • 應用程序模塊清晰明確,整合容易
  • 數(shù)據(jù)維護方便易行,容易定位

垂直切分的缺點

  • 部分表關聯(lián)無法在數(shù)據(jù)庫級別完成,需要在程序中完成
  • 對于訪問極其頻繁且數(shù)據(jù)量超大的表仍然存在性能瓶頸,不一定能滿足要求
  • 事務處理相對更為復雜
  • 切分達到一定程度之后,擴展性會遇到限制
  • 過度切分可能會帶來系統(tǒng)過渡復雜而難以維護

水平分表

何為水平切分?

當一個表中的數(shù)據(jù)量過大時,我們可以把該表的數(shù)據(jù)按照某種規(guī)則,進行劃分,然后存儲到多個結構相同的表,和不同的庫上。
依據(jù)的條件可以是時間、地域、功能等比較清晰的條件

比如財務報表、薪資發(fā)放就可以用時間進行水平分割;

比如商品庫存就可以用地域進行分割

比如用戶表的普通用戶、商戶就可以用功能來進行劃分

水平通用分表策略

以uuid作為全局唯一標識,為每一個新生成的用戶生成uuid
將uuid進行md5加密,生成16進制隨機字符串,取隨機字符串前兩位進行10進制轉換,對分表數(shù)量的取余,獲取插入的表后綴名。
比如建立8張表,對8取余,則會生成user_0...user_7,每個用戶會隨機插入這8張表中

分表后,如何統(tǒng)計數(shù)據(jù)?

id uuid addtime

所有統(tǒng)計數(shù)據(jù)都是根據(jù)業(yè)務需求而來的,原始數(shù)據(jù)存在的情況,我們可以進行自建索引,實現(xiàn)具體的業(yè)務需求。
比如根據(jù)添加時間自建索引,其結構如下:

id uuid addtime

那么根據(jù)addtime 我們就可以得出總數(shù),最新個數(shù)。

分表后查詢效率的問題?

根據(jù)自建索引表,獲取uuid,再根據(jù)uuid獲取數(shù)據(jù)每一行的數(shù)據(jù)。   
只不過多了一個10次的for循環(huán)而已,而php的10for循環(huán)可以說是微秒級的。

結果集存儲的是指針 在通過 mysql_fetch_row()讀取磁盤文件


水平切分的優(yōu)點

  • 表關聯(lián)基本能夠在數(shù)據(jù)庫端全部完成
  • 不會存在某些超大型數(shù)據(jù)量和高負載的表遇到瓶頸的問題
  • 應用程序端整體架構改動相對較少
  • 事務處理相對簡單
  • 只要切分規(guī)則能夠定義好,基本上較難遇到擴展性限制

水平切分的缺點

  • 切分規(guī)則相對更為復雜,很難抽象出一個能夠滿足整個數(shù)據(jù)庫的切分規(guī)則
  • 后期數(shù)據(jù)的維護難度有所增加,人為手工定位數(shù)據(jù)更困難
  • 應用系統(tǒng)各模塊耦合度較高,可能會對后面數(shù)據(jù)的遷移拆分造成一定的困難

垂直與水平聯(lián)合切分的使用 (聯(lián)合切分)

如果大部分業(yè)務邏輯稍微復雜一點,系統(tǒng)負載大一些的系統(tǒng),
都無法通過上面任何一種數(shù)據(jù)的切分方法來實現(xiàn)較好的擴展性,
而需要將上述兩種切分方法結合使用,不同的場景使用不同的切分方法。

聯(lián)合切分的優(yōu)點

  • 可以充分利用垂直切分和水平切分各自的優(yōu)勢而避免各自的缺陷
  • 讓系統(tǒng)擴展性得到最大化提升

聯(lián)合切分的缺點

  • 數(shù)據(jù)庫系統(tǒng)架構比較復雜,維護難度更大;
  • 應用程序架構也相對更復雜;

MySQL 數(shù)據(jù)庫鎖定機制

數(shù)據(jù)庫鎖定機制簡單來說就是數(shù)據(jù)庫為了保證數(shù)據(jù)的一致性而使各種共享資源在被并發(fā)訪問訪問變得有序所設計的一種規(guī)則。對于任何一種數(shù)據(jù)庫來說都需要有相應的鎖定機制。

為了保證數(shù)據(jù)的一致完整性,任何一個數(shù)據(jù)庫都存在鎖定機制。鎖定機制的優(yōu)劣直接影響到一個數(shù)據(jù)庫系統(tǒng)的并發(fā)處理能力和性能,所以鎖定機制的實現(xiàn)也就成為了各種數(shù)據(jù)庫的核心技術之一。

總的來說,MySQL 各存儲引擎使用了三種類型(級別)的鎖定機制:**行級鎖定,頁級鎖定和表級鎖定。

行級鎖定(row-level)

優(yōu)點 :并發(fā)優(yōu)勢
缺點 :耗費資源,容易造成死鎖

行級鎖定最大的特點就是鎖定對象的顆粒度很小,也是目前各大數(shù)據(jù)庫管理軟件所實現(xiàn)的鎖定顆粒度最小的。由于鎖定顆粒度很小,所以發(fā)生鎖定資源爭用的概率也最小,能夠給予應用程序盡可能大的并發(fā)處理能力而提高一些需要高并發(fā)應用系統(tǒng)的整體性能。

雖然能夠在并發(fā)處理能力上面有較大的優(yōu)勢,但是行級鎖定也因此帶來了不少弊端。由于鎖定資源的顆粒度很小,所以每次獲取鎖和釋放鎖需要做的事情也更多,帶來的消耗自然也就更大了。此外,行級鎖定也最容易發(fā)生死鎖。

表級鎖定(table-level)

優(yōu)點 :簡單, 避免死鎖
缺點 :并發(fā)劣勢

和行級鎖定相反,表級別的鎖定是MySQL 各存儲引擎中最大顆粒度的鎖定機制。該鎖定機制最大的特點是實現(xiàn)邏輯非常簡單,帶來的系統(tǒng)負面影響最小。所以獲取鎖和釋放鎖的速度很快。由于表級鎖一次會將整個表鎖定,所以可以很好的避免困擾我們的死鎖問題。

當然,鎖定顆粒度大所帶來最大的負面影響就是出現(xiàn)鎖定資源爭用的概率也會最高,致使并發(fā)度大打折扣。

頁級鎖定(page-level)

BerkeleyDB 引擎使用
頁級鎖定是MySQL 中比較獨特的一種鎖定級別,在其他數(shù)據(jù)庫管理軟件中也并不是太常見。頁級鎖定的特點是鎖定顆粒度介于行級鎖定與表級鎖之間,所以獲取鎖定所需要的資源開銷,以及所能提供的并發(fā)處理能力也同樣是介于上面二者之間。另外,頁級鎖定和行級鎖定一樣,會發(fā)生死鎖。

使用場景

在MySQL 數(shù)據(jù)庫中,
使用表級鎖定的主要是MyISAM,Memory,CSV 等一些非事務性存儲引擎,
而使用行級鎖定的主要是Innodb 存儲引擎和NDB Cluster 存儲引擎,
頁級鎖定主要是BerkeleyDB 存儲引擎的鎖定方式。

MyISAM 表鎖優(yōu)化

縮短鎖定時間,

短短幾個字,說起來確實聽容易的,但實際做起來恐怕就并不那么簡單了。如何讓鎖定時間盡可能的短呢?唯一的辦法就是讓我們的Query 執(zhí)行時間盡可能的短。

a) 盡量減少大的復雜Query,將復雜Query 分拆成幾個小的Query 分步進行;
b) 盡可能的建立足夠高效的索引,讓數(shù)據(jù)檢索更迅速;
c) 盡量讓MyISAM 存儲引擎的表只存放必要的信息,控制字段類型;

分離能并行的操作

MyISAM 的存儲引擎還有一個非常有用的特性,Concurrent Insert(并發(fā)插入)的特性。MyISAM 存儲引擎有一個控制是否打開Concurrent Insert 功能的參數(shù)選項:concurrent_insert,可以設置為0,1 或者2。三個值的具體說明如下:

a) concurrent_insert=2,無論MyISAM 存儲引擎的表數(shù)據(jù)文件的中間部分是否存在因為刪除數(shù)據(jù)而留下的空閑空間,都允許在數(shù)據(jù)文件尾部進行Concurrent Insert;

b) concurrent_insert=1,當MyISAM 存儲引擎表數(shù)據(jù)文件中間不存在空閑空間的時候,可以從文件尾部進行Concurrent Insert;

c) concurrent_insert=0,無論MyISAM 存儲引擎的表數(shù)據(jù)文件的中間部分是否存在因為刪除數(shù)據(jù)而留下的空閑空間,都不允許Concurrent Insert。

Innodb 行鎖優(yōu)化

a) 盡可能讓所有的數(shù)據(jù)檢索都通過索引來完成,從而避免Innodb 因為無法通過索引鍵加鎖而升級為表級鎖定;

b) 合理設計索引,讓Innodb 在索引鍵上面加鎖的時候盡可能準確,盡可能的縮小鎖定范圍,避免造成不必要的鎖定而影響其他Query 的執(zhí)行;

c) 盡可能減少基于范圍的數(shù)據(jù)檢索過濾條件,避免因為間隙鎖帶來的負面影響而鎖定了不該鎖定的記錄;

d) 盡量控制事務的大小,減少鎖定的資源量和鎖定時間長度;

e) 在業(yè)務環(huán)境允許的情況下,盡量使用較低級別的事務隔離,以減MySQL 因為實現(xiàn)事務隔離級別所帶來的附加成本;

減少死鎖產(chǎn)生概率建議

a) 類似業(yè)務模塊中,盡可能按照相同的訪問順序來訪問,防止產(chǎn)生死鎖;

b) 在同一個事務中,盡可能做到一次鎖定所需要的所有資源,減少死鎖產(chǎn)生概率;

c) 對于非常容易產(chǎn)生死鎖的業(yè)務部分,可以嘗試使用升級鎖定顆粒度,通過表級鎖定來減少死鎖產(chǎn)生的概率

死鎖解決

查看正在鎖的事務

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; 

查看等待鎖的事務

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

數(shù)據(jù)類型優(yōu)化

盡量選擇占用空間小的類型

因為小的類型無論是在磁盤,還是在內(nèi)存中占用的空間都是小的,在進行查詢或者排序是臨時表要求的空間也會相對較少。在數(shù)據(jù)量比較小的時候可能感覺不到,但是當數(shù)據(jù)量比較大時,這個原則的重要性可能就會得到顯現(xiàn)。

盡量選擇簡單/恰當?shù)念愋?/h4>

在對表進行選擇以及排序的時候,對于簡單的類型往往只需要消耗較少的CPU時鐘周期。例如,對于MySql server而言,整數(shù)類型值的Compare往往會比字符串類型值的Compare簡單且快,所以當你需要對特定的表進行排序時應該盡量選擇整數(shù)類型作為排序的依據(jù)

盡量將字段設置為NOTNULL

一般情況下,如果你沒有顯示的制定一個字段為NULL,那么這個字段將會被數(shù)據(jù)庫系統(tǒng)認為是NULLABLE, 系統(tǒng)的這種默認行為將會導致以下三個問題
(1) Mysql服務器自身的 查詢優(yōu)化功能將會受影響
(2) Mysql針對null值的字段需要額外的存儲空間以及處理
(3) 如果一個null值是索引的一部分,那么索引的效果也會收到影響

由于這個原則對于數(shù)據(jù)庫性能提升的作用不是很大,
所以對于已經(jīng)存在的DB schema,其存在NULLABLE字段或者是索引為NULLABLE的,
也不用專門的去修改它,
但是對于新設計的DB或者索引需要盡量遵守這個原則。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,250評論 6 530
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,923評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,041評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,475評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,253評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,801評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,882評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,023評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,530評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,494評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,639評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,177評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,890評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,289評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,552評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,242評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,626評論 2 370

推薦閱讀更多精彩內(nèi)容