构建支持Master/Slave读写分离的数据库操作类

 一般对于访问量比较大的网站来说,采用基本的MySQL Master/Slave 结构是很正常,而且一般都是一台Master,多台Slave的情况,但是一般在进行这个访问的时候问题比较多,因为读写操作必须分离,所以今天没事就构造了这个数据库操作类。
数据库操作类基本特点:
1. 支持一台Master,多台Slave的情况,所有SQL能够强制调用Master来处理
2. 能够自动识别是写入还是读取操作,然后自动连接到需要的Master/Slave服务器。 操作过程中能够自动识别,如果没有Slave, 则所有操作都是指向Master的,如果当前连接的Slave无法工作则自动连接其他Slave。对于Slave读取数据采用随机挑选Slave服务器的 方式来读取数据
3. 在同一个PHP程序中能够重用连接,不会重复去连接数据库,减少因为反复连接带来的资源消耗

缺点:
1. 对于Slave是采取随机选择Slave来进行读取数据,所以不是真正意义上的负载均衡
2. 每次初始化都是连接好所有的数据库,比较浪费连接资源和时间。 在多个PHP程序中无法保持数据库连接,每次启动都需要连接到远程数据库,浪费了很多连接处理的时间,但是这个跟PHP是脚本程序有关。
3. 无法支持多个Master的情况,同时可定制性比较差,很多东西都是固定的,必须按照固有模式来执行查询
相关:
代码中注释不是很全,部分注释而且不一定正确,所以如果自己使用,最好进行二次修改。
本数据库类参考了我之前写的《简单快速有趣的MySQL数据库操作类:SimpleDB》,可以对照着看。
(类库中的接口我大部分都经过了测试,都正常使用,如果发现不正常或者有更好的想法,请留言)

文件:db_common.class.php
<?php
//----------------------------------------------------
//       Master/Slave数据库读写分开操作类
//
// 作者: heiyeluren <http://blog.csdn.net/heiyeshuwu>
// 时间: 2007-7-30
// 描述:支持所有写操作在一台Master执行,所有读操作在
//          Slave执行,并且能够支持多台Slave主机
//----------------------------------------------------
    
    
/**
* 常量定义
*/
define("_DB_INSERT", 1);
define("_DB_UPDATE", 2);
    
    
/**
* DB Common class
*
* 描述:能够分别处理一台Master写操作,多台Slave读操作
*/
class DBCommon
{
     /**
      * 数据库配置信息
      */
     var $wdbConf = array();
     var $rdbConf = array();
     /**
      * Master数据库连接
      */
     var $wdbConn = null;
     /**
      * Slave数据库连接
      */
     var $rdbConn = array();
     /**
      * 数据库结果
      */
     var $dbResult;
     /**
      * 数据库查询结果集
      */
     var $dbRecord;

     /**
      * SQL语句
      */
     var $dbSql;
     /**
      * 数据库编码
      */
     var $dbCharset = "UTF8";
     /**
      * 数据库版本
      */
     var $dbVersion = "4.1";


     /**
      * 初始化的时候是否要连接到数据库
      */
     var $isInitConn = false;
     /**
      * 是否要设置字符集
      */
     var $isCharset = true;
     /**
      * 数据库结果集提取方式
      */
     var $fetchMode = MYSQL_ASSOC;
     /**
      * 执行中发生错误是否记录日志
      */
     var $isLog = true;
     /**
      * 是否查询出错的时候终止脚本执行
      */
     var $isExit = false;



     //------------------------
     //
     //   基础的DB操作
     //
     //------------------------

     /**
      * 构造函数
      *
      * 传递配置信息,配置信息数组结构:
      * $masterConf = array(
      *         "host"     => Master数据库主机地址
      *         "user"     => 登录用户名
      *         "pwd"     => 登录密码
      *         "db"     => 默认连接的数据库
      *     );
      * $slaveConf = array(
      *         "host"     => Slave1数据库主机地址|Slave2数据库主机地址|...
      *         "user"     => 登录用户名
      *         "pwd"     => 登录密码
      *         "db"     => 默认连接的数据库
      *     );
      */
     function DBCommon($masterConf, $slaveConf=array()){
         //构造数据库配置信息
         if (is_array($masterConf) && !empty($masterConf)){
             $this->wdbConf = $masterConf;
         }
         if (!is_array($slaveConf) || empty($slaveConf)){
             $this->rdbConf = $masterConf;
         } else {
             $this->rdbConf = $slaveConf;
         }
         //初始化连接(一般不推荐)
         if ($this->isInitConn){
             $this->getDbWriteConn();
             $this->getDbReadConn();
         }
     }

     /**
      * 获取Master的写数据连接
      */
     function getDbWriteConn(){
         //判断是否已经连接
         if ($this->wdbConn && is_resource($this->wdbConn)) {
             return $this->wdbConn;
         }
         //没有连接则自行处理
         $db = $this->connect($this->wdbConf[’host’], $this->wdbConf[’user’], $this->wdbConf[’pwd’], $this->wdbConf[’db’]);
         if (!$db || !is_resource($db)) {
             return false;
         }
         $this->wdbConn = $db;
         return $this->wdbConn;
     }

     /**
      * 获取Slave的读数据连接
      */
     function getDbReadConn(){
         //如果有可用的Slave连接,随机挑选一台Slave
         if (is_array($this->rdbConn) && !empty($this->rdbConn)) {
             $key = array_rand($this->rdbConn);
             if (isset($this->rdbConn[$key]) && is_resource($this->rdbConn[$key])) {
                 return $this->rdbConn[$key];
             }
         }
         //连接到所有Slave数据库,如果没有可用的Slave机则调用Master
         $arrHost = explode("|", $this->rdbConf[’host’]);
         if (!is_array($arrHost) || empty($arrHost)){
             return $this->getDbWriteConn();
         }
         $this->rdbConn = array();
         foreach($arrHost as $tmpHost){
             $db = $this->connect($tmpHost, $this->rdbConf[’user’], $this->rdbConf[’pwd’], $this->rdbConf[’db’]);
             if ($db && is_resource($db)){
                 $this->rdbConn[] = $db;
             }
         }
         //如果没有一台可用的Slave则调用Master
         if (!is_array($this->rdbConn) || empty($this->rdbConn)){
             $this->errorLog("Not availability slave db connection, call master db connection");
             return $this->getDbWriteConn();
         }
         //随机在已连接的Slave机中选择一台
         $key = array_rand($this->rdbConn);
         if (isset($this->rdbConn[$key])   && is_resource($this->rdbConn[$key])){
             return $this->rdbConn[$key];
         }
         //如果选择的slave机器是无效的,并且可用的slave机器大于一台则循环遍历所有能用的slave机器
         if (count($this->rdbConn) > 1){
             foreach($this->rdbConn as $conn){
                 if (is_resource($conn)){
                     return $conn;
                 }
             }
         }
         //如果没有可用的Slave连接,则继续使用Master连接
         return $this->getDbWriteConn();
     }

     /**
      * 连接到MySQL数据库公共方法
      */
     function connect($dbHost, $dbUser, $dbPasswd, $dbDatabase){
         //连接数据库主机
         $db = mysql_connect($dbHost, $dbUser, $dbPasswd);
         if (!$db) {
             $this->errorLog("Mysql connect ". $dbHost ." failed");
             return false;
         }
         //选定数据库
         if (!mysql_select_db($dbDatabase, $db)) {
             $this->errorLog("select db $dbDatabase failed", $db);
             return false;
         }
         //设置字符集
         if ($this->isCharset){
             if ( $this->dbVersion == ’’ ){
                 $res = mysql_query("SELECT VERSION()");
                 $this->dbVersion = mysql_result($res, 0);
             }
        
             if ($this->dbCharset!=’’ && preg_match("/^(5.|4.1)/", $this->dbVersion)){
                 if (mysql_query("SET NAMES ’".$this->dbCharset."’", $db) === false){
                     $this->errorLog("Set db_host ’$dbHost’ charset=". $this->dbCharset ." failed.", $db);
                     return false;
                 }
             }
         }
         return $db;
     }

     /**
      * 关闭数据库连接
      */
     function disconnect($dbConn=null, $closeAll=false){
         //关闭指定数据库连接
         if ($dbConn && is_resource($dbConn)){
             mysql_close($dbConn);
             $dbConn = null;
         }
         //关闭所有数据库连接
         if ($closeAll){
             if ($this->rdbConn && is_resource($rdbConn)){
                 mysql_close($rdbConn);
                 $this->rdbConn = null;
             }
             if (is_array($this->rdbConn) && !empty($this->rdbConn)){
                 foreach($this->rdbConn as $conn){
                     if ($conn && is_resource($conn)){
                         mysql_close($conn);
                     }
                 }
                 $this->rdbConn = array();
             }
         }
         return true;
     }

     /**
      * 选择数据库
      */
     function selectDb($dbName, $dbConn=null){
         //重新选择一个连接的数据库
         if ($dbConn && is_resource($dbConn)){
             if (!mysql_select_db($dbName, $dbConn)){
                 $this->errorLog("Select database:$dbName failed.", $dbConn);
                 return false;
             }
             return true;
         }
         //重新选择所有连接的数据库
         if ($this->wdbConn && is_resource($this->wdbConn)){
             if (!mysql_select_db($dbName, $this->wdbConn)){
                 $this->errorLog("Select database:$dbName failed.", $this->wdbConn);
                 return false;
             }
         }
         if (is_array($this->rdbConn && !empty($this->rdbConn))){
             foreach($this->rdbConn as $conn){
                 if ($conn && is_resource($conn)){
                     if (!mysql_select_db($dbName, $conn)){
                         $this->errorLog("Select database:$dbName failed.", $conn);
                         return false;
                     }
                 }
             }
         }
         return true;
     }

     /**
      * 执行SQL语句(底层操作)
      */
     function query($sql, $isMaster=false){
         if (trim($sql) == ""){
             $this->errorLog("Sql query is empty.");
             return false;
         }
         //获取执行SQL的数据库连接
         if (!$isMaster){
             $optType = trim(strtolower(array_shift(explode(" ", ltrim($sql)))));
         }
         if ($isMaster || $optType!="select"){
             $dbConn = $this->getDbWriteConn();
         } else {
             $dbConn = $this->getDbReadConn();
         }
         if (!$dbConn || !is_resource($dbConn)){
             $this->errorLog("Not availability db connection. Query SQL:". $sql);
             if ($this->isExit) {
                 exit;
             }
             return false;
         }
         //执行查询
         $this->dbSql = $sql;
         $this->dbResult = null;
         $this->dbResult = @mysql_query($sql, $dbConn);
         if ($this->dbResult === false){
             $this->errorLog("Query sql failed. SQL:".$sql, $dbConn);
             if ($this->isExit) {
                 exit;
             }
             return false;
         }
         return true;
     }

     /**
      * 错误日志
      */
     function errorLog($msg=’’, $conn=null){
         if (!$this->isLog){
             return;
         }
         if ($msg==’’ && !$conn) {
             return false;
         }
         $log = "MySQL Error: $msg";
         if ($conn && is_resource($conn)) {
             $log .= " mysql_msg:". mysql_error($conn);
         }
         $log .= " [". date("Y-m-d H:i:s") ."]";
         error_log($log);
         return true;
     }




     //--------------------------
     //
     //        数据获取接口
     //
     //--------------------------
     /**
      * 获取SQL执行的全部结果集(二维数组)
      *
      * @param string $sql 需要执行查询的SQL语句
      * @return 成功返回查询结果的二维数组,失败返回false
      */
     function getAll($sql, $isMaster=false){
         if (!$this->query($sql, $isMaster)){
             return false;
         }
         $this->dbRecord = array();
         while ($row = @mysql_fetch_array($this->dbResult, $this->fetchMode)) {
             $this->dbRecord[] = $row;
         }
         @mysql_free_result($this->dbResult);
         if (!is_array($this->dbRecord) || empty($this->dbRecord)){
             return false;
         }
         return $this->dbRecord;
     }

     /**
      * 获取单行记录(一维数组)
      *
      * @param string $sql 需要执行查询的SQL语句
      * @return 成功返回结果记录的一维数组,失败返回false
      */
     function getRow($sql, $isMaster=false){
         if (!$this->query($sql, $isMaster)){
             return false;
         }
         $this->dbRecord = array();
         $this->dbRecord = @mysql_fetch_array($this->dbResult, $this->fetchMode);
         @mysql_free_result($this->dbResult);
         if (!is_array($this->dbRecord) || empty($this->dbRecord)){
             return false;
         }
         return $this->dbRecord;
     }

     /**
      * 获取一列数据(一维数组)
      *
      * @param string $sql 需要获取的字符串
      * @param string $field 需要获取的列,如果不指定,默认是第一列
      * @return 成功返回提取的结果记录的一维数组,失败返回false
      */
     function getCol($sql, $field=’’, $isMaster=false){
         if (!$this->query($sql, $isMaster)){
             return false;
         }
         $this->dbRecord = array();
         while($row = @mysql_fetch_array($this->dbResult, $this->fetchMode)){
             if (trim($field) == ’’){
                 $this->dbRecord[] = current($row);
             } else {
                 $this->dbRecord[] = $row[$field];
             }
         }
         @mysql_free_result($this->dbResult);
         if (!is_array($this->dbRecord) || empty($this->dbRecord)){
             return false;
         }
         return $this->dbRecord;
     }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值