1. 程式人生 > >MySQL常用分表分庫方式

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