mysql本身用錯索引+給字符串字段加索引


mysql為什么有時會選錯索引

  • 場景例子:一張表里有a,b兩個字段,並分別建立以下索引

    CREATE TABLE `t` (
    `id` int(11) NOT NULL,
    `a` int(11) DEFAULT NULL,
    `b` int(11) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `a` (`a`),
    KEY `b` (`b`)
    ) ENGINE=InnoDB;
    
    • 表中數據從(1,1,1)到(100000,100000,100000)共10萬行記錄。

    • 接下來分析一條sql語句:select * from t where a between 10000 and 20000;

    • 做如下操作:

    • 我們可以設置慢日志閾值為0,表示這個線程接下來的語句都會被記入慢查詢日志中;set long_query_time=0;可以看到執行B時,走了全表掃描,沒有走索引。

  • 為什么沒有按指定的a索引掃描呢?

    • 而優化器選擇索引的目的,是找到一個最優的執行方案,並用最小的代價去執行語句。在數據庫里面,掃描行數是影響執行代價的因素之一。掃描的行數越少,意味着訪問磁盤數據的次數越少,消耗的CPU資源越少。
      當然,掃描行數並不是唯一的判斷標准,優化器還會結合是否使用臨時表、是否排序等因素進行綜合判斷。我們這個簡單的查詢語句並沒有涉及到臨時表和排序,所以MySQL選錯索引肯定是在判斷掃描行數的時候出問題了。
    • 基數:
      • mysql進行掃描前,並不知道滿足這個條件的記錄有多少條,而只能根據統計信息來估算記錄數。即索引區分度,一個索引上不同的值越多,這個區分度越好,而一個索引上不同的值的個數稱為基數,基數越大,索引區分度越好。可以使用show index from 表名,查看結果。
    • 優化器會估算回表的代價,同時評估主鍵索引掃描的沒有額外代價,選擇執行哪個掃描。
    • 使用analyze table 表名 命令,可以重新統計索引信息。糾正錯誤結果
  • 另外一個語句:mysql> select * from t where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1

    • 如果使用索引a進行查詢,那么就是掃描索引a的前1000個值,然后取到對應的id,再到主鍵索引
      上去查出每一行,然后根據字段b來過濾。顯然這樣需要掃描1000行。
      如果使用索引b進行查詢,那么就是掃描索引b的最后50001個值,與上面的執行過程相同,也是
      需要回到主鍵索引上取值再判斷,所以需要掃描50001行。

    • 可以看到,返回結果中key字段顯示,這次優化器選擇了索引b,而rows字段顯示需要掃描的行
      數是50198。
      從這個結果中,你可以得到兩個結論:

      1. 掃描行數的估計值依然不准確;
      2. 這個例子里MySQL又選錯了索引。
    • 原本可以執行得很快的SQL語句,執行速度卻比你預期的慢很多,你應該怎么辦呢?

      • 一種方法是,像我們第一個例子一樣,采用force index強行選擇一個索引。
      • 第二種方法就是,我們可以考慮修改語句,引導MySQL使用我們期望的索引。比如,在這個例子里,顯然把“order by b limit 1” 改成“order by b,a limit 1” ,語義的邏輯是相同的。
        • 之前優化器選擇使用索引b,是因為它認為使用索引b可以避免排序(b本身是索引,已經是有序的了,如果選擇索引b的話,不需要再做排序,只需要遍歷),所以即使掃描行數多,也判定為代價更小。
          現在order by b,a 這種寫法,要求按照b,a排序,就意味着使用這兩個索引都需要排序。因此,掃描行數成了影響決策的主要條件,於是此時優化器選了只需要掃描1000行的索引a。
      • 第三種方法是,在有些場景下,我們可以新建一個更合適的索引,來提供給優化器做選擇,或刪掉誤用的索引。(刪除索引b)

怎么給字符串字段加索引

  • 場景例子:維護一個由郵箱構成的登錄系統數據庫

  • 兩種索引方式:

    • alter table SUser add index index1(email);
      • 這一種包含了每個記錄的整個字符串
    • alter table SUser add index index2(email(6));
      • 對每個記錄只取前6個字節
      • 優點:占用空間小
      • 增加額外的掃描次數
  • 如果使用index1掃描順序

    • sql語句:select id,name,email from SUser where email='hjjxyz@xxx.com'
    1. 先在index1索引樹找到主鍵是ID2的行,判斷email值是否正確,加入到結果集;
    2. 到主鍵上查找主鍵值是ID2的行,判斷email是否正確,加入到結果集
    3. 在index1所引述剛剛查到的位置嚇一跳記錄,發現不滿足條件,循環結束。
    • 整個過程值回了一次表,可以認為只掃描了一行。
  • 如果用index2順序

    1. 在index2索引樹找到滿足索引值是hjjxyz的記錄,找到了ID1
    2. 從主鍵上查找ID1的行,判斷是否滿足where條件,不是則丟棄
    3. 在index2查到的位置繼續查找下一條,仍然是hjjxyz,取出,再到ID索引樹判斷,滿足where條件,加入到結果集
    4. 重復上一步,直到index2不是hjjxyz,循環結束
    • 在整個過程中,回表了多次,相當於掃描了多行
  • 如果將index的長度變長,這樣區分度更大,這樣掃描的次數就減少了,但是占用了空間也變大了,那么如何控制好索引長度呢?

    • 首先 select count(distinct email) as L from SUser;算出這個列有多少不同的值

    • 查看4-7個字節的前綴索引,查出的個數

    • mysql> select
      count(distinct left(email,4))as L4,
      count(distinct left(email,5))as L5,
      count(distinct left(email,6))as L6,
      count(distinct left(email,7))as L7,
      from SUser;
      
    • 如果可以得到95%以上的數據時就可以選取該長度

  • 前綴索引一定要回表,無法使用覆蓋索引

    • 如果用index1可以直接返回結果,不需要回表,但是如果是index2即使字段很長,innodb還是要回表查詢。
  • 如何最大化使用前綴索引提高搜索性能?

    • 比如身份證,前6位如果是一個區的完全相同,區分度不高
    1. 倒敘存儲 select field_list from t where id_card = reverse('身份證號碼')
    2. 使用hash字段,在表上創建一個新字段,來hash保存身份證號的值,重復概率很小,可以認為每次只掃描了一行
    • 這兩種方式都不支持范圍掃描


免責聲明!

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



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