1. 程式人生 > >mysql分庫分表的方法

mysql分庫分表的方法

分表後怎麼做全文搜尋

1.merge方式分表(不好)

2. 使用 sql union

3 使用Sphinx全文檢索引擎

一,先說一下為什麼要分表

當一張的資料達到幾百萬時,你查詢一次所花的時間會變多,如果有聯合查詢的話,我想有可能會死在那兒了。分表的目的就在於此,減小資料庫的負擔,縮短查詢時間。

根據個人經驗,mysql執行一個sql的過程如下:
1,接收到sql;2,把sql放到排隊佇列中 ;3,執行sql;4,返回執行結果。在這個執行過程中最花時間在什麼地方呢?第一,是排隊等待的時間,第二,sql的執行時間。其實這二個是一回事,等待的同時,肯定有sql在執行。所以我們要縮短sql的執行時間。

mysql中有一種機制是表鎖定和行鎖定,為什麼要出現這種機制,是為了保證資料的完整 性,我舉個例子來說吧,如果有二個sql都要修改同一張表的同一條資料,這個時候怎麼辦呢,是不是二個sql都可以同時修改這條資料呢?很顯然mysql 對這種情況的處理是,一種是表鎖定(myisam儲存引擎),一個是行鎖定(innodb儲存引擎)。表鎖定表示你們都不能對這張表進行操作,必須等我對 表操作完才行。行鎖定也一樣,別的sql必須等我對這條資料操作完了,才能對這條資料進行操作。如果資料太多,一次執行的時間太長,等待的時間就越長,這 也是我們為什麼要分表的原因。

垂直分割

就是將一個大表分為多個小表.把主碼和一些列放到一個表,然後把主碼和另外的列放到另一個表中。
如果一個表中某些列常用,而另外一些列不常用,則可以採用垂直分割,另外垂直分割可以使得資料行變小,一個數據頁就能存放更多的資料,在查詢時就會減少I/O次數。其缺點是需要管理冗餘列,查詢所有資料需要join操作。比如物料有很多屬性,不同的部門有不同的屬性需求,比如財務部門有財務的屬性要求,採購部門有采購的屬性要求,按部門要求不同拆分為不同的表,僅將基本的公共屬性放在主表中,根據不同的部門要求建不同的表及查詢檢視,效能要好一些

常見的分表維度考慮

按時間分表

這種分表方式有一定的侷限性,當資料有較強的實效性,如微博傳送記錄、微信訊息記錄等,這種資料很少有使用者會查詢幾個月前的資料,如就可以按月分表。

資料遷移的方式

當一些很久之前的資料,很少再查詢。比如員工工資表,我們可以只存今年的工資情況。而歷史資料我們可以遷移到一張salary_old表中,保證資料不會丟失。但也可以用來查詢。每天定期把今年中的最早一天的記錄歸入舊錶中。這樣一方面可以解決效能問題,最多也只需要讀2張表就完成了。

按熱度拆分

典型的像貼吧這種有高點選率的詞條,也有低點選率的詞條,如果一個詞條一張表,那得多少表啊,所以一般這種情況就會對高點選率的詞條生成 一張表,低熱度的詞條都放在一張大表裡,待低熱度的詞條達到一定的貼數後,比如1W條,再把低熱度的表單獨拆分成一張表。

二,分表

1,做mysql叢集,例如:利用mysql cluster ,mysql proxy,mysql replication,drdb等等

有人會問mysql叢集,根分表有什麼關係嗎?雖然它不是實際意義上的分表,但是它啟到 了分表的作用,做叢集的意義是什麼呢?為一個數據庫減輕負擔,說白了就是減少sql排隊佇列中的sql的數量,舉個例子:有10個sql請求,如果放在一 個數據庫伺服器的排隊佇列中,他要等很長時間,如果把這10個sql請求,分配到5個數據庫伺服器的排隊佇列中,一個數據庫伺服器的佇列中只有2個,這樣 等待時間是不是大大的縮短了呢?這已經很明顯了。所以我把它列到了分表的範圍以內,我做過一些mysql的叢集:

優點:擴充套件性好,沒有多個分表後的複雜操作(php程式碼)

缺點:單個表的資料量還是沒有變,一次操作所花的時間還是那麼多,硬體開銷大。

2,預先估計會出現大資料量並且訪問頻繁的表,將其分為若干個表

使用MD5雜湊

做法是對UID進行md5加密,然後取前幾位(我們這裡取前兩位),然後就可以將不同的UID雜湊到不同的使用者表(user_xx)中了

  1. <?php  
  2. function get_hash_table($table, $userid)  
  3. {  
  4.     $str = crc32($userid);  
  5.     if ($str < 0) {  
  6.         $hash = "0" . substr(abs($str), 0, 1);  
  7.     } else {  
  8.         $hash = substr($str, 0, 2);  
  9.     }  
  10.     return $table . "_" . $hash;  
  11. }  
  12. //echo get_hash_table('message', 'user18991'); //結果為message_10  
  13. //echo get_hash_table('message', 'user34523'); //結果為message_13  
  14. function calc_hash_db($u, $s = 4) {  
  15.     $h = sprintf("%u", crc32($u));  
  16.     $h1 = intval(fmod($h, $s));  
  17.     return $h1;  
  18. }  
  19. for ($i = 1; $i < 40; $i++) {  
  20.     echo calc_hash_tbl($i);  
  21.     echo "<br>";  
  22.     echo calc_hash_db($i);  
  23.     echo "<br>";  
  24. }  
  25. function calc_hash_tbl($u, $n = 256, $m = 16) {  
  26.     $h = sprintf("%u", crc32($u));  
  27.     $h1 = intval($h / $n);  
  28.     $h2 = $h1 % $n;  
  29.     $h3 = base_convert($h2, 10, $m);  
  30.     $h4 = sprintf("%02s", $h3);  
  31.     return $h4;  
  32. }  
  33. #################  
  34. function getTable( $uid ){  
  35.     $ext = substr ( md5($uid) ,0 ,2 );  
  36.     return "user_".$ext;  
  37. }  
  38. ###################  
  39. private function getDbNo($email)  
  40. {  
  41.     $m = md5($email);  
  42.     $n = hexdec(substr($m, 0, 16));  
  43.     $tableNo = fmod($n, 1000);  
  44.     $dbNo = $tableNo % 100;  
  45.     return array($dbNo, $tableNo);  
  46. }  

通過這個技巧,我們可以將不同的UID分散到256中使用者表中,分別是user_00,user_01 ……    user_ff。因為UID是數字且遞增,根據md5的演算法,可以將使用者資料幾乎很均勻的分別到不同的user表中。

但是這裡有個問題是,如果我們的系統的使用者越來越多,勢必單張表的資料量越來越大,而且根據這種演算法無法擴充套件表,這又會回到文章開頭出現的問題了。

使用移位

  1. /** 
  2.  * 根據UID分表演算法 
  3.  * 
  4.  * @param int $uid  //使用者ID 
  5.  * @param int $bit    //表字尾保留幾位 
  6.  * @param int $seed //向右移動位數 
  7.  */  
  8. function getTable( $uid , $bit , $seed ){  
  9.     return "user_" . sprintf( "%0{$bit}d" , ($uid >> $seed) );  
  10.     return "user_" . sprintf( "%04d", ($uid >> 20) );  
  11. }  

這裡,我們將uid向右移動20位,這樣我們就可以把大約前100萬的使用者資料放在第一個表user_0000,第二個100萬的使用者資料放在第二 個表user_0001中,這樣一直下去,如果我們的使用者越來越多,直接新增使用者表就行了。由於我們保留的表字尾是四位,這裡我們可以新增1萬張使用者表, 即user_0000,user_0001 …… user_9999。一萬張表,每張表100萬資料,我們可以存100億條使用者記錄。當然,如果你的使用者資料比這還多,也不要緊,你只要改變保留表字尾來 增加可以擴充套件的表就行了,如如果有1000億條資料,每個表存100萬,那麼你需要10萬張表,我們只要保留表字尾為6位即可。

上面兩種方法,都要對我們當前系統的使用者資料量做出可能最大的預估,並且對資料庫單個表的最大承受量做出預估。

比如第二種方案,如果我們預估我們系統的使用者是100億,單張表的最優資料量是100萬,那麼我們就需要將UID移動20來確保每個表是100萬的資料,保留使用者表(user_xxxx)四位來擴充套件1萬張表。

又如第一種方案,每張表100萬,md5後取前兩位,就只能有256張表了,系統總資料庫就是:256*100萬;如果你係統的總資料量的比這還多,那你實現肯定要MD5取前三位或者四位甚至更多位了。

兩種方法都是將資料水平切分到不同的表中,相對第一種方法,第二種方法更具擴充套件性。

3,利用merge儲存引擎來實現分表

我覺得這種方法比較適合,那些沒有事先考慮,而已經出現了得,資料查詢慢的情況。這個時 候如果要把已有的大資料量表分開比較痛苦,最痛苦的事就是改程式碼,因為程式裡面的sql語句已經寫好了,現在一張表要分成幾十張表,甚至上百張表,這樣 sql語句是不是要重寫呢?舉個例子,我很喜歡舉子

mysql>show engines;的時候你會發現mrg_myisam其實就是merge。

  1. CREATE TABLE IF NOT EXISTS `user1` (    
  2.     `id` int(11) NOT NULL AUTO_INCREMENT,    
  3.     `name` varchar(50) DEFAULT NULL,    
  4.     `sex` int(1) NOT NULL DEFAULT '0',    
  5.     PRIMARY KEY (`id`)    
  6.   ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;    
  7. Query OK, 0 rows affected (0.05 sec)  
  8.  CREATE TABLE IF NOT EXISTS `user2` (    
  9.     `id` int(11) NOT NULL AUTO_INCREMENT,    
  10.     `name` varchar(50) DEFAULT NULL,    
  11.     `sex` int(1) NOT NULL DEFAULT '0',    
  12.     PRIMARY KEY (`id`)    
  13.   ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;    
  14. Query OK, 0 rows affected (0.01 sec)  
  15. mysql> INSERT INTO `user1` (`name`, `sex`) VALUES('張映', 0);  
  16. Query OK, 1 row affected (0.00 sec)  
  17. mysql> INSERT INTO `user2` (`name`, `sex`) VALUES('tank', 1);  
  18. Query OK, 1 row affected (0.00 sec)  
  19. CREATE TABLE IF NOT EXISTS `alluser` (  
  20.     `id` int(11) NOT NULL AUTO_INCREMENT,  
  21.     `name` varchar(50) DEFAULT NULL,  
  22.     `sex` int(1) NOT NULL DEFAULT '0',  
  23.     INDEX(id)  
  24.   ) TYPE=MERGE UNION=(user1,user2) INSERT_METHOD=LAST AUTO_INCREMENT=1 ;  
  25. Query OK, 0 rows affected, 1 warning (0.00 sec)  
  26. mysql> select id,name,sex from alluser;  
  27. +----+--------+-----+  
  28. | id | name   | sex |  
  29. +----+--------+-----+  
  30. |  1 | 張映 |   0 |  
  31. |  1 | tank   |   1 |  
  32. +----+--------+-----+  
  33. 2 rows in set (0.00 sec)  
  34. mysql> INSERT INTO `alluser` (`name`, `sex`) VALUES('tank2', 0);  
  35. Query OK, 1 row affected (0.00 sec)  
  36. mysql> select id,name,sex from user2  
  37.  -> ;  
  38. +----+-------+-----+  
  39. | id | name  | sex |  
  40. +----+-------+-----+  
  41. |  1 | tank  |   1 |  
  42. |  2 | tank2 |   0 |  
  43. +----+-------+-----+  
  44. 2 rows in set (0.00 sec)  

從上面的操作中,我不知道你有沒有發現點什麼?假如我有一張使用者表user,有50W條資料,現在要拆成二張表user1和user2,每張表25W條資料,

INSERT INTO user1(user1.id,user1.name,user1.sex)SELECT (user.id,user.name,user.sex)FROM user where user.id <= 250000

INSERT INTO user2(user2.id,user2.name,user2.sex)SELECT (user.id,user.name,user.sex)FROM user where user.id > 250000

這樣我就成功的將一張user表,分成了二個表,這個時候有一個問題,程式碼中的sql語 句怎麼辦,以前是一張表,現在變成二張表了,程式碼改動很大,這樣給程式設計師帶來了很大的工作量,有沒有好的辦法解決這一點呢?辦法是把以前的user表備份 一下,然後刪除掉,上面的操作中我建立了一個alluser表,只把這個alluser表的表名改成user就行了。但是,不是所有的mysql操作都能 用的

a,如果你使用 alter table 來把 merge 表變為其它表型別,到底層表的對映就被丟失了。取而代之的,來自底層 myisam 表的行被複制到已更換的表中,該表隨後被指定新型別。

b,網上看到一些說replace不起作用,我試了一下可以起作用的。暈一個先

  1. mysql> UPDATE alluser SET sex=REPLACE(sex, 0, 1) where id=2;    
  2. Query OK, 1 row affected (0.00 sec)    
  3.  Rows matched: 1  Changed: 1  Warnings: 0    
  4.  mysql> select * from alluser;    
  5.  +----+--------+-----+    
  6.  | id | name   | sex |    
  7.  +----+--------+-----+    
  8.  |  1 | 張映 |   0 |    
  9.  |  1 | tank   |   1 |    
  10.  |  2 | tank2  |   1 |    
  11.  +----+--------+-----+    
  12.  3 rows in set (0.00 sec)    

c,一個 merge 表不能在整個表上維持 unique 約束。當你執行一個 insert,資料進入第一個或者最後一個 myisam 表(取決於 insert_method 選項的值)。mysql 確保唯一鍵值在那個 myisam 表裡保持唯一,但不是跨集合裡所有的表。

d,當你建立一個 merge 表之時,沒有檢查去確保底層表的存在以及有相同的機構。當 merge 表被使用之時,mysql 檢查每個被對映的表的記錄長度是否相等,但這並不十分可靠。如果你從不相似的 myisam 表建立一個 merge 表,你非常有可能撞見奇怪的問題。

好睏睡覺了,c和d在網上看到的,沒有測試,大家試一下吧。

優點:擴充套件性好,並且程式程式碼改動的不是很大

缺點:這種方法的效果比第二種要差一點

三,總結一下

上面提到的三種方法,我實際做過二種,第一種和第二種。第三種沒有做過,所以說的細一 點。哈哈。做什麼事都有一個度,超過個度就過變得很差,不能一味的做資料庫伺服器叢集,硬體是要花錢買的,也不要一味的分表,分出來1000 表,mysql的儲存歸根到底還以檔案的形勢存在硬碟上面,一張表對應三個檔案,1000個分表就是對應3000個檔案,這樣檢索起來也會變的很慢 。我的 建議是

方法1和方法2結合的方式來進行分表

方法1和方法3結合的方式來進行分表

我的二個建議適合不同的情況,根據個人情況而定,我覺得會有很多人選擇方法1和方法3結合的方式

分庫分表產生的問題,及注意事項
1. 分庫分表維度的問題
假如使用者購買了商品,需要將交易記錄儲存取來,如果按照使用者的緯度分表,則每個使用者的交易記錄都儲存在同一表中,所以很快很方便的查詢到某使用者的購買情況,但是某商品被購買的情況則很有可能分佈在多張表中,查詢起來比較麻煩。反之,按照商品維度分表,可以很方便的查詢到此商品的購買情況,但要查詢到買人的交易記錄比較麻煩。 
所以常見的解決方式有:
     a.通過掃表的方式解決,此方法基本不可能,效率太低了。
     b.記錄兩份資料,一份按照使用者緯度分表,一份按照商品維度分表。
     c.通過搜尋引擎解決,但如果實時性要求很高,又得關係到實時搜尋。
2. 聯合查詢的問題
聯合查詢基本不可能,因為關聯的表有可能不在同一資料庫中。 
3.   避免跨庫事務
避免在一個事務中修改db0中的表的時候同時修改db1中的表,一個是操作起來更復雜,效率也會有一定影響。
4.   儘量把同一組資料放到同一DB伺服器上
例如將賣家a的商品和交易資訊都放到db0中,當db1掛了的時候,賣家a相關的東西可以正常使用。也就是說避免資料庫中的資料依賴另一資料庫中的資料。