mysql分庫分表實戰及php代碼操作完整實例


**當單表達到幾千萬時,查詢一次要很久,如果有聯合查詢,有可能會死在那
分庫分表主要就是解決這個問題,減小數據庫的負擔,縮短查詢時間**

分庫:
1)按功能分
用戶類庫、商品類庫、訂單類庫、日志類、統計類庫…
2)按地區分
每個城市或省市一個同樣的庫,加上后綴或前綴如:db_click_bj、db_click_sh…

分表:
1、橫向分表 解決表記錄太大問題
1)按某個字段分,
如:discuz的附件表分成10個附件分表pre_forum_attachment_0到pre_forum_attachment_9,還有1個附件索引表pre_forum_attachment存儲tid和附件id關系
根據主題的tid最后一位來決定附件要保存在哪個分表,
2)按日期分表
一些日志、統計類的可以按年、月、日、周分表
如:點擊量統計click_201601、click_201602
3)使用mysql的merge
先把分表創建好,然后創建總表指定engine= MERGE UNION=(table1,table2) INSERT_METHOD = LAST;
2、縱向分表 解決列過多問題
1)經常組合查詢的列放在一個表,常用字段的表可考慮Memory引擎
2)不經常使用的字段單獨成表
3)把text、blob等大字段拆分放在附表
如:phpcms的文章表分成主表v9_news和從表v9_news_data,主表存標題、關鍵字、瀏覽量等,從表存具體內容、模板等

很多主流mvc框架都提供了切換數據庫訪問方法

thinkphp切換數據庫

$this->db(1,"mysql://root:123456@localhost:3306/test")->query("查詢sql");//數據庫連接信息可放在配置文件
$this->db(1)->query("查詢sql");//下次直接使用編號1定位

分表的話 在查詢前先根據分表規則把表名查出

演示
這里用兩台機子簡單以同個業務庫分庫,同個表分表,演示插入、查詢如何定位庫和表並最終成功執行

兩台機子:

server1:192.168.1.198

server2:192.168.1.199

兩台機子都執行下面操作

1、先創建10個數據庫,每個數據庫10個表

當然也可以改成百庫百表,也可手動創建,我為了方便寫了個腳本批量創建

create.php

<?php
ini_set('memory_limit', '-1');
$con=mysql_connect("192.168.1.198","open","123456");
if($con){ 
for($i=0;$i<10;$i++){//10個庫
$sql="drop database cloude_{$i};";//刪庫 謹慎
mysql_query($sql);
$sql="create database cloude_{$i} default character set utf8 collate utf8_general_ci;";
$do=mysql_query($sql,$con)or die(mysql_error());
if($do){
mysql_select_db("cloude_{$i}",$con);
mysql_query("set name gtf8");
for($j=0;$j<10;$j++){ //10個表
$sql="drop table if exists user_{$j};";
mysql_query($sql);
$sql="create table user_{$j}
(
id char(36) not null primary key,
name char(15) not null default '',
password char(32) not null default '',
sex char(1) not null default '男'
)engine=InnoDB;";
$do=mysql_query($sql,$con) or die(mysql_error());
if($do){
//echo "create table user_{$j} successful! <br/>";
}else{
//echo "create error!";
}
}
}
}
}else{
echo "connect error!!!!";
}

2、分庫分表路由實現
Config.php

<?php
class Config{ 
public $dsn; 
public $user; 
public $password; 
public $dbname; //分庫分表后得到的數據庫名
public $table; //分庫分表后得到的表名
private static $config;//mysql配置數組
private static $configFile = 'mysql.php'; //配置文件路徑

public function __construct($dbname, $table, $id = 0){ 
if (is_null(static::$config)) { 
$config = include(static::$configFile); 
static::$config = $config; 
}

$config = static::$config; 
if (isset($config['shared']) && isset($config['shared'][$dbname])) { 
$dbconfig = $config['shared'][$dbname]; 
$id = is_numeric($id) ? (int)$id : crc32($id); 
$database_id = ($id / $dbconfig['database_split'][0]) % $dbconfig['database_split'][1]; 
$table_id = ($id / $dbconfig['table_split'][0]) % $dbconfig['table_split'][1];

foreach ($dbconfig['host'] as $key => $conf) { 
list($from, $to) = explode('-', $key); 
if ($from <= $database_id && $database_id <= $to) { 
$the_config = $conf; 
} 
}

$this->dbname = $dbname . '_' . $database_id; 
$this->table = $table . '_' . $table_id; 
} else { 
$this->dbname = $dbname; 
$this->table = $table; 
$the_config = $config['db'][$dbname]; 
} 
$c = $the_config; 
if (isset($c['unix_socket']) && $c['unix_socket']) { 
$this->dsn = sprintf('mysql:dbname=%s;unix_socket=%s', $this->dbname, $c['unix_socket']); 
} else { 
$this->dsn = sprintf('mysql:dbname=%s;host=%s;port=%s', $this->dbname, $c['host'], $c['port']); 
} 
$this->user = $c['user']; 
$this->password = $c['password']; 
}

}

3、數據庫配置文件
mysql.php

<?php
$default = array( 
'unix_socket' => null, 
'host' => '192.168.1.198', 
'port' => '3306', 
'user' => 'open', 
'password' => '123456', 
); 
$db_199 = array( 
'unix_socket' => null, 
'host' => '192.168.1.199', 
'port' => '3306', 
'user' => 'open', 
'password' => '123456', 
); 
$config = array( 
// 不進行分庫分表的數據庫 
'db' => array( 
'hadoop' => $default, 
), 
// 分庫分表 
'shared' => array( 
'cloude' => array( 
'host' => array( 
/** 
* 編號為 0 到 4 的庫使用的鏈接配置 
*/ 
'0-4' => $default, 
/** 
* 編號為 5 到 9 的庫使用的鏈接配置 
*/ 
'5-9' => $db_199,

),

// 分庫分表規則 
/** 
* 下面的配置對應10庫10表
* 如果根據 uid 進行分表,假設 uid 為 224,對應的庫表為: 
* (224 / 1) % 10 = 4 為編號為 4 的庫 
* (224 / 10) % 10 = 1 為編號為 2 的表 
*/ 
'database_split' => array(1, 10), 
'table_split' => array(10, 10), 
), 
), 
); 
return $config;

4、模型類操作數據庫
Model.php

<?php
require_once 'Config.php';//引入配置信息
class Model{ 
public $config; //數據庫配置
public $connection; //pdo
protected $dbnamePrefix; //庫前綴如cloude_50 前綴為cloude 
protected $tablePrefix; //表前綴
protected $dbname; //分庫分表后對應的庫
protected $table; //分庫分表后對應的庫表

public function __construct($id){ 
$this->config = new Config($this->dbnamePrefix, $this->tablePrefix, $id); //根據id找到對應庫和表
$this->connection = new Pdo($this->config->dsn, $this->config->user, $this->config->password);//實例化pdo 
$this->connection->exec("set names utf8"); 
$this->dbname = $this->config->dbname;
$this->table = $this->config->table; 
}

public function update(array $data, array $where = array()){

}

public function select(array $condition){ 
$sqlwhere='';
if(!empty($condition)){ 
foreach ($condition as $field => $value) { 
$where[] = '`'.$field.'`='."'".addslashes($value)."'"; 
} 
$sqlwhere .= ' '.implode(' and ', $where); 
}
$sql="select * from ".$this->dbname.'.'.$this->table;
if($sqlwhere){
$sql.=" where $sqlwhere";
}
$res=$this->connection->query($sql);
$data['data']=$res->fetchAll(PDO::FETCH_ASSOC);
$data['info']=array("dsn"=>$this->config->dsn,"dbname"=>$this->dbname,"table"=>$this->table,"sql"=>$sql);
return $data; 
} 
public function insert(array $arrData) {
$name = $values = '';
$flag = $flagV = 1;
$true = is_array( current($arrData) );//判斷是否一次插入多條數據
if($true) {
//構建插入多條數據的sql語句
foreach($arrData as $arr) {
$values .= $flag ? '(' : ',(';
foreach($arr as $key => $value) {
if($flagV) {
if($flag) $name .= "$key";
$values .= "'$value'";
$flagV = 0;
} else {
if($flag) $name .= ",$key";
$values .= ",'$value'";
}
}
$values .= ') ';
$flag = 0;
$flagV = 1;
}
} else {
//構建插入單條數據的sql語句
foreach($arrData as $key => $value) {
if($flagV) {
$name = "$key";
$values = "('$value'";
$flagV = 0;
} else {
$name .= ",$key";
$values .= ",'$value'";
}
}
$values .= ") ";
}

$sql = "insert into ".$this->dbname.'.'.$this->table." ($name) values $values";
if( ($rs = $this->connection->exec($sql) ) > 0 ) {
return array("dsn"=>$this->config->dsn,"dbname"=>$this->dbname,"table"=>$this->table,"sql"=>$sql);
}
return false;
}
public function query($sql){ 
return $this->connection->query($sql); 
} 
}
1

5、測試
使用主鍵id作為分表字段,那最好就不要使用自增了,可使用uuid

User.php

<?php
require 'Config.php'; 
require 'Model.php'; 
class User extends Model 
{ 
protected $dbnamePrefix = 'cloude'; 
protected $tablePrefix = 'user'; 
} 
//生成唯一uuid
function create_uuid($prefix = ""){ //可以指定前綴
$str = md5(uniqid(mt_rand(), true)); 
$uuid = substr($str,0,8) . '-'; 
$uuid .= substr($str,8,4) . '-'; 
$uuid .= substr($str,12,4) . '-'; 
$uuid .= substr($str,16,4) . '-'; 
$uuid .= substr($str,20,12); 
return $prefix . $uuid;
}

$userId=create_uuid();
$user = new User($userId);
$data=array('id'=>$userId,'name'=>'大明'.$userId,'password'=>'14e1b600b1fd579f47433b88e8d85291','sex'=>'男'); 
if($result=$user->insert($data)){
echo '插入成功:','<pre/>';
print_r($result);
}

$condition=array("id"=>$userId);
$list=$user->select($condition);
if($list){
echo '查詢成功:','<pre/>';
print_r($list);
}

6、結果
插入成功會返回插入到哪個庫哪個表,查詢成功返回從哪個庫哪個表查的

 

 


**

分庫分表注意事項:
**
1、維度問題
假如用戶購買了商品,需 要將交易記錄保存取來,如果按照用戶的緯度分表,則每個用戶的交易記錄都保存在同一表中,所以很快很方便的查找到某用戶的購買情況,但是某商品被購買的情 況則很有可能分布在多張表中,查找起來比較麻煩。反之,按照商品維度分表,可以很方便的查找到此商品的購買情況,但要查找到買人的交易記錄比較麻煩。
所以常見的解決方式有:
通過掃表的方式解決,此方法基本不可能,效率太低了。
記錄兩份數據,一份按照用戶緯度分表,一份按照商品維度分表。
通過搜索引擎解決,但如果實時性要求很高,又得關系到實時搜索
2、避免分表join操作 因為關聯的表有可能不在同一數據庫中
3、避免跨庫事務
避免在一個事務中修改db0中的表的時候同時修改db1中的表,一個是操作起來更復雜,效率也會有一定影響
4、分表宜多不宜少;這樣做主要是為了盡量避免后期可能遇到的二次拆分
5、盡量把同一組數據放到同一DB服務器上
例如將賣家a的商品和交易信息都放到db0中,當db1掛了的時候,賣家a相關的東西可以正常使用。也就是說避免數據庫中的數據依賴另一數據庫中的數據
————————————————
版權聲明:本文為CSDN博主「justPr」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_38642740/java/article/details/81448762


免責聲明!

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



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