1. 程式人生 > >【資料庫】Mysql中主鍵的幾種表設計組合的實際應用效果

【資料庫】Mysql中主鍵的幾種表設計組合的實際應用效果

寫在前面

        前前後後忙忙碌碌,度過了新工作的三個月。部落格許久未新,似乎對忙碌沒有一點點防備。總結下來三個月不斷的磨礪自己,努力從獨樂樂轉變到眾樂樂,體會到不一樣的是,連辦公室的新玩意都能引起莫名的興趣了,作為一隻忙碌的 “猿” 倒不知正常與否。

        咳咳, 正題, 今天要寫一篇關於mysql的主鍵、索引的文章,mysql的研究博主進行還不夠深入,今天討論的主題主要是,主鍵對增刪改查的具體影響是什麼? 博主將用具體的實驗說明

         如果你不瞭解主鍵,你可以先看看下面的小節,否則你可以直接跳轉到實驗步驟

瞭解主鍵、外來鍵、索引

主鍵

  主鍵的主要作用是保證表的完整、保證表資料行的唯一性質,

     ① 業務主鍵(自然主鍵):在資料庫表中把具有業務邏輯含義的欄位作為主鍵,稱為自然主鍵(Natural Key)”

   自然主鍵的含義就是原始資料中存在的不重複欄位,直接使用成為主鍵欄位。 這種方式對業務的耦合太強,一般不會使用。

     ② 邏輯主鍵(代理主鍵):在資料庫表中採用一個與當前表中邏輯資訊無關的欄位作為其主鍵,稱為代理主鍵

          邏輯主鍵提供了一個與當前表資料邏輯無關的欄位作為主鍵,邏輯主鍵被廣泛使用在業務表、資料表,一般有幾種生成方式:uuid、自增。其中使用最多的是自增,邏輯主鍵成功的避免了主鍵與資料表關聯耦合的問題,與業務主鍵不同的是,業務主鍵的資料一旦發生更改,那麼那個系統中關於主鍵的所有資訊都需要連帶修改,這是不可避免的,並且這個更改是隨業務需求的增量而不斷的增加、膨脹。而邏輯主鍵與應用耦合度低,它與資料無任何必要的關係,你可以只關心:第一條資料; 而不用關心: 名字是a的那條資料。  某一天名字改成b, 你還是隻關心:第一條資料。

         業務的更改幾乎是不可避免的,前期任何產品經理言之鑿鑿的不修改論調都是不可靠、不切實際的。我們必須考慮主鍵資料在更改的情況下,資料能否平穩度過危機。

     ② 複合主鍵(聯合主鍵):通過兩個或者多個欄位的組合作為主鍵。

    複合主鍵可以說是業務主鍵的升級版本,通常一個業務欄位不能夠確定一條資料的唯一性,例如 張三的身份證是34123322, 張三這種大眾名稱100%會出現重複。我們可以用姓名 + 身份證的方式表示主鍵,宣告一個唯一的記錄。

    有時候,複合主鍵是複雜的。 姓名+身份證 不一定能表示不重複,雖然身份證在17年消除了重複的問題,但是之前的資料呢? 可能我們需要新增一個地址作為聯合主鍵,例如 姓名 + 身份證 + 聯絡地址確認一個人的身份。在其他的業務中,例如訪問控制,使用者 + 終端 + 終端型別 + 站點 + 頁面 + 時間,可能六個欄位的聯合才能夠去確定一個欄位的唯一性,這另複雜度陡升。

    另外如果其他表要與該表關聯則需要引用複合主鍵的所有欄位,這就不單純是效能問題了,還有儲存空間的問題了,當然你也可以認為這是合理的資料冗餘,方便查詢,但是感覺有點得不償失。

    使用複合主鍵的原因可能是:對於關係表來說必須關聯兩個實體表的主鍵,才能表示它們之間的關係,那麼可以把這兩個主鍵聯合組成複合主鍵即可。

    如果兩個實體存在多個關係,可以再加一個順序欄位聯合組成複合主鍵,但是這樣就會引入業務主鍵的弊端。當然也可以另外對這個關係表新增一個邏輯主鍵,避免了業務主鍵的弊端,同時也方便其他表對它的引用。

外來鍵

       外來鍵是一種約束,表與表的關聯約束,例如a表依賴關聯b表的某個欄位,你可以設定a表字段外來鍵關聯到b表的欄位,將兩張表強制關聯起來,這時候產生兩個效果

               ① 表 b 無法被刪除,你必須先刪除a表

               ② 新增的資料必須與表b某行關聯

       這對某些需要強耦合的業務操作來說很有必要,、 要強調但是,外來鍵約束我認為,不可濫用,沒有合適的理由支撐它的使用的話,將導致業務強制耦合。另外對開發人員不夠友好。使用外來鍵一定不能超過3表相互。否則將引出很多的麻煩而不得不取消外來鍵。

索引

      索引用於快速找出在某個列中有一特定值的行,不使用索引,MySQL必須從第一條記錄開始讀完整個表,直到找出相關的行,表越大,查詢資料所花費的時間就越多,如果表中查詢的列有一個索引,MySQL能夠快速到達一個位置去搜索資料檔案,而不必檢視所有資料,那麼將會節省很大一部分時間。

  例如:有一張person表,其中有2W條記錄,記錄著2W個人的資訊。有一個Phone的欄位記錄每個人的電話號碼,現在想要查詢出電話號碼為xxxx的人的資訊。

  如果沒有索引,那麼將從表中第一條記錄一條條往下遍歷,直到找到該條資訊為止。

  如果有了索引,那麼會將該Phone欄位,通過一定的方法進行儲存,好讓查詢該欄位上的資訊時,能夠快速找到對應的資料,而不必在遍歷2W條資料了。其中MySQL中的索引的儲存型別有兩種BTREE、HASH。 也就是用樹或者Hash值來儲存該欄位,要知道其中詳細是如何查詢的,就需要會演算法的知識了。我們現在只需要知道索引的作用,功能是什麼就行。

        優點:

    1、所有的MySql列型別(欄位型別)都可以被索引,也就是可以給任意欄位設定索引

    2、大大加快資料的查詢速度

  缺點:

    1、建立索引和維護索引要耗費時間,並且隨著資料量的增加所耗費的時間也會增加

    2、索引也需要佔空間,我們知道資料表中的資料也會有最大上線設定的,如果我們有大量的索引,索引檔案可能會比資料檔案更快達到上線值

    3、當對錶中的資料進行增加、刪除、修改時,索引也需要動態的維護,降低了資料的維護速度。

  使用原則:

    索引需要合理的使用。

    1、對經常更新的表就避免對其進行過多的索引,對經常用於查詢的欄位應該建立索引,

    2、資料量小的表最好不要使用索引,因為由於資料較少,可能查詢全部資料花費的時間比遍歷索引的時間還要短,索引就可能不會產生優化效果。

    3、在一同值少的列上(欄位上)不要建立索引,比如在學生表的"性別"欄位上只有男,女兩個不同值。相反的,在一個欄位上不同值較多可是建立索引。

測試主鍵的影響力

       為了說明業務主鍵、邏輯主鍵、複合主鍵對資料表的影響力,博主使用java生成四組測試資料,首先準備表結構為:

  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,  -- 自增
  `dt` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,     -- 使用uuid模擬不同的id
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  -- 隨機名稱
  `age` int(10) NULL DEFAULT NULL,   -- 隨機數生成年齡
  `key` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  -- 唯一標識 使用uuid測試
  PRIMARY KEY (`id`) USING BTREE -- 設定主鍵

  將生成四組千萬條的資料: 

        1. 自增主鍵   test_primary_a 

        2. 自增主鍵  有索引 test_primary_d 

        3. 無主鍵 無索引 test_primary_b 

        4. 複合主鍵 無索引 test_primary_c 

       使用java, spring boot + mybatis每次批量一萬條資料,插入一千次,記錄每次插入時間,總插入時間:

      mybatis程式碼:

<insert id="insertTestData">
        insert into test_primary_${code} (
        `dt`,
        `name`,
        `age`,
        `key`
        ) values
        <foreach collection="items" item="item"  index= "index" separator =",">
            (
            #{item.dt},
            #{item.name},
            #{item.age},
            #{item.key}
            )
        </foreach>

        java程式碼,使用了mybatis外掛提供的事務處理:

@Transactional(readOnly = false)
   public Object testPrimary (String type) {
       HashMap result = new HashMap();
       // 記錄總耗時 開始時間
       long start = new Date().getTime();
       // 記錄總耗時 插入條數
       int len = 0;
       try{
           String[] names = {"趙一", "錢二", "張三" , "李四", "王五", "宋六", "陳七", "孫八", "歐陽九" , "徐10"};
           for (int w = 0; w < 1000; w++) {
               // 記錄萬條耗時
               long startMil = new Date().getTime();

               ArrayList<HashMap> items = new ArrayList<>();
               for (int i = 0; i < 10000; i++) {
                   String dt = StringUtils.uuid();
                   String key = StringUtils.uuid();
                   int age = (int)((Math.random() * 9 + 1) * 10); // 隨機兩位
                   String name = names[(int)(Math.random() * 9 + 1)];
                   HashMap item = new HashMap<>();
                   item.put("dt", dt);
                   item.put("key", key);
                   item.put("age", age);
                   item.put("name", name);
                   items.add(item);
               }
               len += tspTagbodyMapper.insertTestData(items, type);
               long endMil = new Date().getTime();
               // 萬條最終耗時
               result.put(w, endMil - startMil);
           }
           long end = new Date().getTime();
           // 總耗時
           result.put("all", end - start);
           result.put("len", len);
           return result;
       } catch (Exception e) {
           System.out.println(e.toString());
           result.put("e", e.toString());
       }
       return result;
   }

最終生成的資料表情況

        1. 自增主鍵   test_primary_a  ----------  資料長度  960MB

             62分鐘插入一千萬條資料  平均一萬條資料插入 4秒

        2. 自增主鍵  有索引 test_primary_d    資料長度  1GB    索引長度  1.36GB

            75分鐘插入一千萬條資料  平均一萬條資料插入 4.5秒

        3. 無主鍵 無索引 test_primary_b   -----------   資料長度  960MB

             65分鐘插入一千萬條資料  平均一萬條資料插入 4.2秒

        4. 複合主鍵 無索引 test_primary_c    -----------   資料長度  1.54GB

             219分鐘插入一千萬條資料 平均一萬條資料插入 8秒, 這裡有一個問題, 複合主鍵的資料插入耗時是線性增長的,當資料小於100萬 插入時常在五秒左右, 當資料變大,插入時長無限變大,在1000萬條資料時,平均插入一萬資料秒數已經達到15秒了。

        

 查詢速度

         注意索引的建立時以name欄位為開頭,索引的生效第一個條件必須是name

         簡單查詢:

         select name,age from test_primary_a where age=20   -- 自增主鍵 無索引 結果條數11萬 平均3.5秒

         select name,age from test_primary_a where name='張三' and age=20   -- 自增主鍵 有索引 結果條數11萬 平均650豪秒

         select name,age from test_primary_b where age=20   -- 無主鍵 無索引 結果條數11萬 平均7秒

         select name,age from test_primary_c where age=20    -- 聯合主鍵 無索引 結果條數11萬 平均4.5秒

   

         稍複雜條件:

         select name,age,`key`,dt from test_primary_a where age=20 and (name='王五' or name = '張三') and dt like '%abc%'      -- 自增主鍵 無索引 結果條數198 平均4.2秒

    select dt,name,age,`key` from test_primary_d where  (name='王五' or name = '張三') and age=20 and dt like '%abc%'      -- 自增主鍵 有索引 結果條數204 平均650豪秒

         select name,age,`key`,dt from test_primary_d where age=20 and (name='王五' or name = '張三') and dt like '%abc%'      -- 無主鍵 無索引 結果條數194 平均5.9秒

         select name,age,`key`,dt from test_primary_c where age=20 and (name='王五' or name = '張三') and dt like '%abc%'      -- 聯合主鍵 無索引 結果條數11萬 平均5秒

   這樣的語句更誇張一點:

         select name,age,dt from test_primary_c where dt like '%0000%' and name='張三'        -- 聯合主鍵 無索引 結果條數359 平均8秒

          select name,age,dt from test_primary_c where dt like '%0000%' and name='張三'        -- 自增主鍵 有索引 結果條數400 平均1秒

   

初步結論

      從實際應用中可以看出:用各主鍵的對比,在匯入速度上,在前期百萬資料時,各表表現一致,在百萬資料以後,複合主鍵的新增時長將線性增長,應該是因為每一條新增都需要判斷是否重複,而資料量一旦增大,每次新增都需要全表篩查。

      另外一點,邏輯主鍵 + 索引的方式佔用空間一共2.4G, 複合主鍵佔用1.54G 相差大約1個G , 但是實際查詢效果看起來索引更勝一籌,只要查詢方法得當,索引應該是當前的首選。

      最後,關於複合主鍵的作用? 我想應該是在業務主鍵欄位不超過2-3個的情況下,需要確保資料維度的唯一性,採取複合主鍵加上限制。

寫在最後

       前後耗時一整天,完成了這次實驗過程,目的就是檢驗幾種表設計組合的實際應用效果,關於其他的問題,博主將在後續持續跟進。

        實踐出真知