Mysql 知識回顧總結-索引

索引

1. 索引的影響

ttps://img-blog.csdnimg.cn/20200623135902174.png)
MySQL環境:CPU 8 MEM 16G IOPS 300
表數據量:6W行數據,大小74M左右。

select * from xxx where a='xxxx'; #唯一一行

先讀取該表前1000行,讀入其數據頁入緩存。
該字段非索引耗時:0.198 sec
該字段爲索引(唯一/非唯一):0.000 sec

1.1 執行計劃

不使用索引:
在這裏插入圖片描述使用唯一索引:
在這裏插入圖片描述
使用普通索引:在這裏插入圖片描述總結:
索引對性能有影響,且隨着數據量變大,而效果明顯。

2. 普通索引、唯一索引、主鍵索引性能

2.1 主鍵索引與非主鍵索引

在這裏插入圖片描述

當在表上面定義了PRIMARY KEY之後,InnoDB會把它作爲聚集索引;
爲此,爲你的每個表定義一個PRIMARY KEY。如果沒有唯一併且非空的字段或者一組列,那麼請添加一個自增列;
如果您沒有爲表定義PRIMARY KEY,則MySQL會找到第一個不帶null值的UNIQUE索引,並其用作聚集索引;
如果表沒有PRIMARY KEY或沒有合適的UNIQUE索引,則InnoDB 內部會生成一個隱藏的聚集索引GEN_CLUST_INDEX,作爲行ID,行ID是一個6字節的字段,隨着數據的插入而自增;

性能:這個幾乎是其他索引操作數據CURD的必經之路(部分查詢除外)

2.2 查詢的性能

查詢操作

普通索引:查找到滿足條件的第一個記錄後,需要查找下一個記錄,直到碰到第一個不滿足條件的記錄;
唯一索引:由於索引定義了唯一性,查找到第一個滿足條件的記錄後,就會停止繼續檢索;
性能差距:微乎其微 (k=5剛好是該索引頁最後一條記錄,需要讀取下個索引頁影響稍微大點,但是概率低)。

2.3 插入/更新的性能

changge buff介紹:
在不影響數據一致性前提下(個人存疑? 不需要讀入就可以執行的更新 如果where帶條件是否就不行了),InnoDB 會將更新操作緩存在change buffer中,從而減少磁盤的讀入,在下次訪問該數據時,讀入內存併合並。
change buffer 用的是 buffer pool 裏的內存,因此不能無限增大。change buffer 的大小,可以通過參數 innodb_change_buffer_max_size 來動態設置。這個參數設置爲 50 的時候,表示 change buffer 的大小最多隻能佔用 buffer pool 的 50%。

插入一條key = 4 的數據

這個記錄要插入的目標頁在內存中:
唯一索引來說,找到 3 和 5 之間的位置,判斷到沒有衝突,插入這個值,語句執行結束;
普通索引來說,找到 3 和 5 之間的位置,插入這個值,語句執行結束;
性能差異:普通索引和唯一索引對更新語句性能影響的差別,只是一個判斷,只會耗費微小的 CPU 時間。

這個記錄要更新的目標頁不在內存中:
對於唯一索引來說,需要將數據頁讀入內存,判斷到沒有衝突,插入這個值,語句執行結束;
對於普通索引來說,則是將更新記錄在 change buffer,語句執行就結束了。

總結:
業務代碼已經保證不會寫入重複數據”的情況下,使用普通索引(性能更好,使用change buffer減少了磁盤隨機讀)。
業務代碼需要從數據庫保障唯一情況下,還是使用唯一索引。

3. 字符串類型如何加索引

3.1 前綴索引

alter table SUser add index index1(order_no(6));

優勢: 索引結構中的key佔用空間會更小,那麼每個索引頁上能夠放下更多的索引。
劣勢:
增加額外的記錄掃描次數(前綴的區分度越低,掃描到滿足索引條件數據會越多,回表的次數也會變多)
使用前綴索引,定義好長度(區分度),就可以做到既節省空間,又不用額外增加太多的查詢成本。
經驗:一般使用前綴索引能夠過濾掉95%以上不同數據即可,再網上過濾 考慮每增加1%需要多使用的字段長度是否值。
無法使用覆蓋索引:前綴索引不知道是否該字段爲全部字段,需要進行回表確認。
總結:
使用前綴索引,定義好長度,就可以做到既節省空間,又不用額外增加太多的查詢成本。

3.2 倒敘索引

select field_list from t where id_card = reverse('input_id_card_string');

使用hash映射:
劣:每次CPU消耗(可以程序端做),倒序存儲,再創建前綴索引,用於繞過字符串本身前綴的區分度不夠的問題。

3.3 hash索引

select field_list from t where id_card_crc=crc32('input_id_card_string') and id_card='input_id_card_string'

劣:需要數據庫增加一個額外的hash字段,且需要在該字段添加索引,同時在每次查詢時候,需要調用一次crc32 hash函數進行計算,增加CPU消耗(比倒序消耗高);
優:hash衝突概率小,平均查詢行數約爲1,不支持範圍查詢,hash字段僅能等值查詢;
二次優化:程序hash優化(hash函數自定義規則),數據庫直接存。

4. 爲什麼有時候不使用索引

4.1 order by的情況

問題:在sale_time建立普通索引,想利用索引來進行排序,發現並未使用索引。
示例:在這裏插入圖片描述
原因:優化器認爲原字段無序,走二級索引再回表成本比全表掃描高,所以選擇走全表掃描,再排序。
解決:使用limit (如果limit的值比較小,優化器認爲索引有序回表查比回表高,即是沒有二級索引 mysql5.6 針對order by

4.2 存在非索引字段的多條件過濾。

select t1.f1, t1.f2, t2.f1 ,t2.f2 from t1 left jion t2 on t1.index_1 = t2.index_2
where t1.index1 = xxxx and t1.f3=xxx , t1.f4=xxxx and t2.f3=xxxx order by t2.f4 ASSC LIMIT 100

執行結果:
優化器選擇表1全表掃描,滿足規則的放入臨時表,而不使用index1,因使用了非索引字段f3,優化器會因爲f3字段必須回表查詢,而選擇全表掃描

優化:
將 index1 與 f3 建立聯合索引,讓優化器使用索引下推的判斷,從而使表1使用上索引。

sql
 select * from t where (a between 1 and 1000)  and (b between 50000 and 100000) order by b limit 1;

4.3 多索引或部分非索引where條件

在這裏插入圖片描述在這裏插入圖片描述問題:a,b兩個索引,但是優化器選擇了b,原因在於優化器認爲使用索引B遍歷的數據條數更少。
優化1:sql語句添加添加force index
在這裏插入圖片描述優化2:添加排序,因爲二級索引有序,優化器會考慮使用索引

select * from t where (a between 1 and 1000)  and (b between 50000 and 100000) order by b,a limit 1;
```sql
select * from t where (a between 1 and 1000)  and (b between 50000 and 100000) order by b,a limit 1;

優化3:針對性修改sql語句

select * from  (select * from t where (a between 1 and 1000)  and (b between 50000 and 100000) order b

優化4:新建一個更適合的索引,或刪掉誤用的索引。

4.4 在字段上使用函數操作

4.4.1 直接函數操作

mysql> select count(*) from tradelog where month(t_modified)=7;

在這裏插入圖片描述結論:對索引字段做函數操作,可能會破壞索引值的有序性,因此優化器就決定放棄走樹搜索功能。
優化:
mysql> select count(*) from tradelog where
-> (t_modified >= ‘2016-7-1’ and t_modified<‘2016-8-1’) or
-> (t_modified >= ‘2017-7-1’ and t_modified<‘2017-8-1’) or
-> (t_modified >= ‘2018-7-1’ and t_modified<‘2018-8-1’);

4.4.2 隱式類型轉換

mysql> select * from tradelog where tradeid=110717;

實際爲:

mysql> select * from tradelog where  CAST(tradid AS signed int) = 110717;

4.4.3 隱式編碼集轉換

mysql> select d.* from tradelog l, trade_detail d where d.tradeid=l.tradeid and l.id=2; /*語句Q1*/

實際爲:

select * from trade_detail  where CONVERT(traideid USING utf8mb4)=$L2.tradeid.value; 

-------------------以下不屬於索引 ,待挪至其他總結------------------------------------------------

如何隨機取一行

select word from words order by rand() limit 3;

在這裏插入圖片描述
如果內存足夠(tmp_table_size 參數配置),使用內存臨時表實現邏輯:

  1. 創建一個臨時表。這個臨時表使用的是 memory 引擎,表裏有兩個字段,第一個字段是 double 類型,爲了後面描述方便,記爲字段 R,第二個字段是 varchar(64) 類型,記爲字段 W。並且,這個表沒有建索引。
  2. 從 words 表中,按主鍵順序取出所有的 word 值。對於每一個 word 值,調用 rand() 函數生成一個大於 0 小於 1 的隨機小數,並把這個隨機小數和 word 分別存入臨時表的 R 和 W 字段中,到此,掃描行數是 10000。
  3. 現在臨時表有 10000 行數據了,接下來你要在這個沒有索引的內存臨時表上,按照字段 R 排序。
  4. 初始化 sort_buffer。sort_buffer 中有兩個字段,一個是 double 類型,另一個是整型。
  5. 從內存臨時表中一行一行地取出 R 值和位置信息(我後面會和你解釋這裏爲什麼是“位置信息”),分別存入 sort_buffer 中的兩個字段裏。這個過程要對內存臨時表做全表掃描,此時掃描行數增加 10000,變成了 20000。
  6. 在 sort_buffer 中根據 R 的值進行排序。注意,這個過程沒有涉及到表操作,所以不會增加掃描行數。
  7. 排序完成後,取出前三個結果的位置信息,依次到內存臨時表中取出 word 值,返回給客戶端。這個過程中,訪問了表的三行數據,總掃描行數變成了 20003。

如果內存不足,則使用磁盤臨時表(默認是 InnoDB,是由參數 internal_tmp_disk_storage_engine 控制),但是在排序和limit的情況下,採用是 MySQL 5.6 版本引入的一個新的排序算法,即:優先隊列排序算法,其實僅需要維護一個最大堆即可(要求不大於sort_buffer_size大小)。

總結:無論如何,這種取隨機幾個數的操作,都將導致大量排序,嚴重影響性能和資源消耗。

優化1:

select count(*) from t;
### 程序中獲取不大於count(*)的隨機整數a,b,c
select * from t limit a,1
select * from t limit b,1
select * from t limit c,1

一次獲取多個隨機行(前提返回數據不是很多):

select count(*) from t;
### 程序中獲取不大於count(*)的隨機整數假設(a > b > c)
select * from t limit c, a-c +1 
好處:減少掃描行數,壞處,返回的數量量變大了

改進思考:
讀入數據庫數據,放入redis,每次從redis中讀取(利用hash值)

數據

  1. bigint(1)和bigint(19)都能存儲2^64-1範圍內的值。
  2. int是2^32-1。

事務隔離性問題

  1. 程序都建議使用RC作爲默認事務隔壁級別
  2. Mysql 默認事務隔離級別是RR

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章