MySQL 普通索引和唯一索引的區別詳解

2022-09-24 10:27:11 字數 3447 閱讀 7404

普通索引可重複,唯一索引和主鍵一樣不能重複。 唯一索引可作為資料的乙個合法驗證手段,例如學生表的身份證號碼字段,我們人為規定該欄位不得重複,那麼就使用唯一索引。(一般設定學號字段為主鍵)

主鍵保證資料庫裡面的每一行都是唯一的,比如身份證,學號等,在表中要求唯一,不重複。唯一索引的作用跟主鍵的作用一樣。 不同的是,在一張表裡面只能有乙個主鍵,主鍵不能為空,唯一索引可以有多個,唯一索引可以有一條記錄為空,即保證跟別人不一樣就行。 比如學生表,在學校裡面一般用學號做主鍵,身份證則弄成唯一索引;而到了教育局,他們就把身份證號弄成主鍵,學號換成了唯一索引。 選誰做表的主鍵,要看實際應用,主鍵不能為空。

某居民系統,每人有唯一身份證號。如果系統需要按身份證號查姓名,就會執行類似如下sql:

select name from cuser where id_card = 'ooxx';

然後你肯定會在id_card欄位建索引。但id_card欄位較大,不推薦將其做主鍵。於是現有倆選擇:

假定業務**已保證不會寫入重複的身份證號,這兩個選擇邏輯上都正確。但從效能角度考慮,唯一索引還是普通索引呢?

再看如下案例:假設字段 k 上的值都不重複。

接下來分析效能。

select id from t where k=4

通過b+樹從樹根開始層序遍歷到葉節點,可認為資料頁內部是通過二分法搜尋。

看起來效能差距很微小。

innodb資料按資料頁單位讀寫。即讀一條記錄時,並非將該乙個記錄從磁碟讀出,而以頁為單位,將其整體讀入記憶體。

因此普通索引,要多做一次「查詢和判斷下一條記錄」的操作,也就一次指標尋找和一次計算。 如果k=4記錄恰為該資料頁最後乙個記錄,那麼要取下個記錄,還得讀取下個資料頁,操作稍微複雜。 對整型字段,乙個資料頁可存近千key,因此這種情況概率其實也很低。因此計算平均效能差異時,可認為該操作成本對現在cpu開銷忽略不計。

我們知道 mysql 有 change buffer。

現在來看往表中插入乙個新記錄(4,400),innodb會做什麼?

需要區分該記錄要更新的目標頁是否在記憶體:

找到3和5之間位置,判斷到沒有衝突,插入值,語句執行結束。

找到3和5之間位置,插入值,語句執行結束。

普通索引和唯一索引對更新語句效能影響的差別,只是乙個判斷,耗費微小cpu時間。

需將資料頁讀入記憶體,判斷到沒有衝突,插入值,語句執行結束。

將更新記錄在change buffer,語句執行結束。

將資料從磁碟讀入記憶體涉及隨機io訪問,是資料庫裡面成本最高操作之一。而change buffer減少隨機磁碟訪問,所以更新效能提公升明顯。

普通索引和唯一索引究竟如何抉擇?這兩類索引在查詢效能上沒差別,主要考慮對更新效能影響。所以,推薦盡量選擇普通索引。

如果所有更新後面,都緊跟對該記錄的查詢,那麼該關閉change buffer。 而在其他情況下,change buffer都能提公升更新效能。 普通索引和change buffer的配合使用,對於資料量大的表的更新優化還是很明顯的。

在使用機械硬碟時,change bswlyyfnuffer機制的收效非常顯著。 所以,當你有乙個類似「歷史資料」的庫,並且出於成本考慮用機械硬碟時,應該關注這些表裡的索引,盡量使用普通索引,把change buffer 開大,確保「歷史資料」表的資料寫速度。

wal 提公升效能的核心機制,也是儘量減少隨機讀寫,這兩個概念易混淆。 所以,這裡我把它們放到了同乙個流程裡來說明區分。

insert into t(id,k) values(id1,k1),(id2,k2);

假設當前k索引樹的狀態,查詢到位置後,k1所在資料頁在記憶體(innodb buffer pool),k2資料頁不在記憶體。

該更新做了如下操作:

之後事務完成。執行該更新語句成本很低,只寫兩處記憶體,然後寫一處磁碟(前兩次操作合在一起寫了一次磁碟),還是順序寫。

select * from t where k in (k1, k2);

讀語句緊隨更新語句,記憶體中的資料都還在,此時這倆讀操作就與系統表空間和 redo log 無關。所以在圖中就沒畫這倆。

讀page1時,直接從記憶體返回。 wal之後如果讀資料,是不是一定要讀盤,是不是一定要從redo log裡面把資料更新以後才可以返回?其實不用。 看上圖狀態,雖然磁碟上還是之前資料,但這裡直接從記憶體返回結果,結果正確。

要讀page2時,需把page2從磁碟讀入記憶體,然後應用change buffer裡面的操作日誌,生成乙個正確版本並返回結果。 可見直到需讀page2時,該資料頁才被讀入記憶體。

所以,要簡單對比這倆機制對更新效能影響

由於唯一索引用不了change buffer的優化機制,因此如果業務可以接受,從效能角度,推薦優先考慮非唯一索引。

主要糾結在「業務可能無法確保」。本文前提是「業務**已經保證不會寫入重複資料」下,討論效能問題。

如果業務不能保證,或者業務就是www.cppcns.com要求資料庫來做約束,那麼沒得選,必須建立唯一索引。這種情況下,本文意義在於,如果碰上大量插入資料慢、記憶體命中率低時,多提供乙個排查思路。

然後,在一些「歸檔庫」的場景,可考慮使用唯一索引的。比如,線上資料只需保留半年,然後歷史資料儲存在歸檔庫。此時,歸檔資料已是確保沒有唯一鍵衝突。要提高歸檔效率,可考慮把錶的唯一索引改普通索引。

不會丟失。 雖然是只更新記憶體,但在事務提交時,我們把change buffer的操作也記錄到redo log,所以崩潰恢復時,change buffer也能找回。

merge執行流程

該redo log包含資料的變更和change buffer的變更

至此merge過程結束。 這時,資料頁和記憶體中change buffer對應磁碟位置都尚未修改,是髒頁,之後各自刷回自己物理資料,就是另外一過程。

在構造第乙個例子的過程,通過session a的配合,讓session b刪除資料後又重新插入一遍資料,然後就發現explain結果中,rows欄位從10001變成37000多。 而如果沒有session a的配合,只是單獨執行delete from t 、call idata()、explain這三句話,會看到rows欄位其實還是10000左右。這是什麼原因呢?

如果沒有復現,檢查

為什麼經過這個操作序列,explain的結果就不對了? delete 語句刪掉了所有的資料,然後再通過call idata()插入了10萬行資料,看上去是覆蓋了原來10萬行。 但是,session a開啟了事務並沒有提交,所以之前插入的10萬行資料是不能刪除的。這樣,之前的資料每行資料都有兩個版本,舊版本是delete之前資料,新版本是標記deleted的資料。 這樣,索引a上的資料其實有兩份。

然後你會說,不對啊,主鍵上的資料也不能刪,那沒有使用force index的語句,使用explain命令看到的掃瞄行數為什麼還是100000左右?(潛台詞,如果這個也翻倍,也許優化器還會認為選字段a作為索引更合適) 是的,不過這個是主鍵,主鍵是直接按照表的行數來估計的。而表的行數,優化器直接用的是show table status的值。 大家的機器如果io能力比較差的話,做這個驗證的時候,可以把innodb_flush_log_at_trx_commit和sync_binlog都設定成0。

MySQL 普通索引 唯一索引和主索引

1 普通索引 普通索引 由關鍵字key或index定義的索引 的唯一任務是加快對資料的訪問速度。因此,應該只為那些最經常出現在查詢條件 wherecolumn 或排序條件 orderbycolumn 中的資料列建立索引。只要有可能,就應該選擇乙個資料最整齊 最緊湊的資料列 如乙個整數型別的資料列 來...

MySQL 普通索引 唯一索引和主索引

1.普通索引 普通索引 由關鍵字key或index定義的索引 的唯一任務是加快對資料的訪問速度。因此,應該只為那些最經常出現在查詢條件 where column 或排序條件 order by column 中的資料列建立索引。只要有可能,就應該選擇乙個資料最整齊 最緊湊的資料列 如乙個整數型別的資料...

MySQL 普通索引 唯一索引和主索引

1 普通索引 普通索引 由關鍵字key或index定義的索引 的唯一任務是加快對資料的訪問速度。因此,應該只為那些最經常出現在查詢條件 wherecolumn 或排序條件 orderbycolumn 中的資料列建立索引。只要有可能,就應該選擇乙個資料最整齊 最緊湊的資料列 如乙個整數型別的資料列 來...