MySQL常用分表分庫方式
[TOC]
MySQL常用分表分庫方式
一般都喜歡分表要麼藉助第三方工具,比如MySQL各種各樣的中介軟體.這樣會比較省事.
但是不同的中介軟體也有不同的要求和坑,中介軟體對資料一直要求高,容易出問題.
這裡推薦之前用過的中介軟體360的Atla和MyCat,挺不錯的.
垂直分表
- 垂直分表就是對錶進行豎著切一刀
垂直分表一般都沒有什麼難度, 比如將20個欄位的表,砍掉10個挪到其他表.
如: 假設一個簡單的使用者表 user
CREATE TABLE `user_test` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', `invite_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '邀請人id', `user_email` char(60) NOT NULL COMMENT '登入郵箱', `reg_type` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '使用者註冊標識', `user_mobile` char(15) NOT NULL DEFAULT '0' COMMENT '登入手機', `auth_level` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '使用者認證等級', `nickname` char(15) NOT NULL DEFAULT '' COMMENT '暱稱', `password` char(100) NOT NULL DEFAULT '' COMMENT '密碼', `trade_password` char(100) NOT NULL DEFAULT '' COMMENT '交易密碼', `activite_status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '狀態(0-未啟用/1-啟用)', `delete_status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '狀態(0-正常狀態/1-準備刪除)', `vip_level` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT 'VIP等級', `notify_mobile` char(15) NOT NULL DEFAULT '0' COMMENT '訂閱手機', `notify_email` char(60) NOT NULL DEFAULT '' COMMENT '訂閱郵箱', `name` char(32) NOT NULL DEFAULT '' COMMENT '姓名', `id_number` char(18) NOT NULL DEFAULT '0' COMMENT '身份證號', `id_type` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '證件型別', `auth_status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '實名認證狀態(0-未實名/1-實名成功/2-失敗)', `auth_coutry` char(30) NOT NULL DEFAULT '0' COMMENT '實名認證國籍', `auth_msg` varchar(250) NOT NULL DEFAULT '' COMMENT '實名認證稽核評語', `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '建立時間', `create_ip` char(15) NOT NULL DEFAULT '0.0.0.0' COMMENT '建立ip', `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新時間', `update_ip` char(15) NOT NULL DEFAULT '0.0.0.0' COMMENT '更新ip', PRIMARY KEY (`id`), KEY `intive_id` (`intive_id`), KEY `user_email` (`user_email`) USING BTREE, KEY `user_mobile` (`user_mobile`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='使用者';
可以看到上面這個表實際上是比較大的(有的公司可能不嫌大)
實際上所有的使用者資訊都放在一起的話比這個還要大的多,畢竟這只是一個使用者表的案例.
當這個表再大的時候就需要分表了(實際上一般在設計之初就會把使用者的資訊定為兩個表)
一個必備資訊表: user
; 一個非必備資訊表: user_info
; 或者說,一個是熱資料經常要用,一個沒那麼熱不常用.
user
表
CREATE TABLE `user` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', `invite_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '邀請人id', `user_email` char(60) NOT NULL COMMENT '登入郵箱', `reg_type` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '使用者註冊標識', `user_mobile` char(15) NOT NULL DEFAULT '0' COMMENT '登入手機', `auth_level` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '使用者認證等級', `nickname` char(15) NOT NULL DEFAULT '' COMMENT '暱稱', `password` char(32) NOT NULL DEFAULT '' COMMENT '密碼', `pay_password` char(32) NOT NULL DEFAULT '' COMMENT '支付密碼', `lock_status` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '狀態(0-未鎖定賬戶/1-賬戶鎖定)', `activite_status` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '狀態(0-未啟用/1-啟用)', `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '建立時間', `create_ip` char(15) NOT NULL DEFAULT '0.0.0.0' COMMENT '建立ip', `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新時間', `update_ip` char(15) NOT NULL DEFAULT '0.0.0.0' COMMENT '更新ip', PRIMARY KEY (`id`), KEY `intive_id` (`intive_id`), KEY `user_email` (`user_email`) USING BTREE, KEY `user_mobile` (`user_mobile`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='使用者';
user_info
表
CREATE TABLE `user_info` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '使用者ID', `notify_mobile` char(15) NOT NULL DEFAULT '0' COMMENT '訂閱手機', `notify_email` char(60) NOT NULL DEFAULT '' COMMENT '訂閱郵箱', `name` char(15) NOT NULL DEFAULT '' COMMENT '姓名', `id_number` char(18) NOT NULL DEFAULT '0' COMMENT '身份證號', `id_type` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '證件型別', `auth_status` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '實名認證狀態(0-未實名/1-實名成功/2-失敗)', `auth_coutry` char(30) NOT NULL DEFAULT '0' COMMENT '實名認證國籍', `auth_msg` varchar(250) NOT NULL DEFAULT '' COMMENT '實名認證稽核評語', `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '建立時間', `create_ip` char(15) NOT NULL DEFAULT '0.0.0.0' COMMENT '建立ip', `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新時間', `update_ip` char(15) NOT NULL DEFAULT '0.0.0.0' COMMENT '更新ip', PRIMARY KEY (`id`), KEY `user_id` (`user_id`), KEY `user_email` (`user_email`) USING BTREE, KEY `user_mobile` (`user_mobile`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='使用者';
基本上垂直分表沒有什麼難度,按照欄位熱度切割就行了.
如果真的都很熱的話直接做快取吧,修改的時候只修改資料庫,直接把快取刪了.
水平分表
水平分表也沒有什麼難度, 稍微有點難的地方, 為分表後的查詢工作.
主要分表方式
- 按日期分表
- 取模演算法
- 雜湊演算法
- 按日期分表
簡單表結構,假設這樣的表有10個,從money_0到money_9,每個表中存1000萬個使用者的資訊.每個使用者不止一條資料
CREATE TABLE `money_0` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT 'uid',
`country` char(10) NOT NULL DEFAULT '' COMMENT '國家代號',
`money` decimal(22,2) NOT NULL DEFAULT '0.0000000000' COMMENT '鎖定資產',
PRIMARY KEY (`id`),
UNIQUE KEY `uid_country` (`user_id`,`coutry`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
按日期分表
這個很簡單,基本沒有什麼難度. 比如每個月一張表,把新增表的表名設定為該月的時間戳或時間格式即可.
<?php
$tableNameFormt = 'money_';
echo $tableNameFormt . date('Y-m', strtotime('today')). PHP_EOL;
取模演算法- 按範圍分
注意: 大整數溢位問題.
- 簡單的取模方式
直接用使用者id最後一位和表的個數取餘,是幾就放到哪個表中.
這種方式被除數如果不是5和10的話做不到平均分配
而且分10個表要取最後一位,100個表要去最後兩位,以此類推.
所以需要提前把所有表都準備好,如果直接用id做的話要注意大整數的溢位問題.
- 複雜的取模方式
這種方式比如每10萬用戶一個表,當用戶增長到1個億,就需要1000個表.注意要維護表的不斷新增.
<?php
$userId = 1963341; //使用者id
$rowCount = 100000; //每個表存多少資料
$tableNameFormt = 'money_'; //表名格式
$tableIndex = ($userId - ($userId % $rowCount)) / $rowCount; //計算該使用者的資訊儲存在哪個表中
$tableName = $tableNameFormt . $tableIndex; //拼裝完整的表名
echo $tableName . PHP_EOL;
雜湊演算法- 按範圍分
案例1
<?php
$userId = 1963341;
$crcInt = crc32($userId); //注意:crc32()在64位和32位的結果不一樣,crc32返回的結果在32位機上會產生溢位,所以結果可能為負數
$tableNameFormt = 'money_';
if ( $str < 0 ) {
//容錯處理
$hashId = "0".substr(abs($crcInt), 0, 1);
} else {
//正常獲取到的hash
$hashId = substr($crcInt, 0, 2);
}
//拼裝表名
$tableName = $hashId . $hashId;
案例2
<?php
/**
* getTableName 獲取使用者資料儲存在哪個表中.
*
* @param [int] $id 使用者ID
* @param [int] $tableCount 分成多少個表
*
* @return bool
*/
function getTableName($id, $tableCount)
{
$md5 = md5($id);
$str1 = substr($md5, 0, 2);
$str2 = substr($md5, -2, 2);
$newStr = intval(intval($str1.$str2, 16));
$hashID = $newStr % $tableCount + 1;
return