数据库备份与恢复

jerry thinkphp 2015年11月18日 收藏
一个thinkphp操作类,用来导出mysql数据和导入备份
  1. <?php
  2. class DBAction extends CommonAction{
  3.     private $ds = "\n\r\n\r";
  4.     // 每条sql语句的结尾符
  5.     public $sqlEnd = ";";
  6.     public function index(){
  7.         $M = M();
  8.         $tabs = $M->query('SHOW TABLE STATUS');
  9.         $total = 0;
  10.         foreach ($tabs as $k => $v) {
  11.             $tabs[$k]['size'] = byteFormat($v['Data_length'] + $v['Index_length']);
  12.             $total+=$v['Data_length'] + $v['Index_length'];
  13.         }
  14.         //echo __APP__."/Public/data/";
  15.    // echo  $this->export($tablename="");
  16.  //  echo $this->restore("./Public/backup/20130704091642_tao_group_v1.sql");
  17.         $this->assign("list", $tabs);
  18.         $this->assign("total", byteFormat($tota ));
  19.         $this->assign("tables", count($tabs));
  20.         $this->display();
  21.     }
  22.     public function import(){
  23.         //echo C("DB_BACKUP");
  24.         //print_r(glob(C("DB_BACKUP")."*.sql"))
  25.         foreach (glob(C("DB_BACKUP")."*.sql") as $filename) {
  26.     $arr[]=array("filename"=>$filename,"size"=>filesize($filename));
  27.  }
  28. // if(!empty($_GET["file"])){
  29. //     $this->restore(I("file"));
  30. // }
  31.         $this->assign("list", $arr);
  32.      $this->display();    
  33.     }
  34.     public function truncate(){
  35.         $table=I("table");
  36.         if(!empty($table)){
  37.         $M = M();
  38.          $result = $M->query('TRUNCATE TABLE '.$table);
  39.        
  40.         if(empty($result)){
  41.             $this->success("清空表成功!");
  42.         }else{
  43.             $this->error("清空表失败!");
  44.           }
  45.         }
  46.     }
  47.     public function delete(){
  48.         $table=I("table");
  49.         if(!empty($table)){
  50.         $M = M();
  51.          $result = $M->query('DROP TABLE '.$table);
  52.        
  53.         if($result){
  54.             $this->success("删除表成功!");
  55.         }else{
  56.             $this->error("删除表失败!");
  57.           }
  58.         }
  59.     }
  60.     /**
  61.      * getTables 获取数据库表列表
  62.      * @return array $tables      返回结果数组    
  63.      */
  64.     public function getTables() {
  65.         $M = M();
  66.         $res = $M->query ( "SHOW TABLES;" );
  67.         $tables = array ();
  68.         foreach ( $res as $row ) {
  69.             
  70.             foreach ($row as $v){
  71.                 $tables[]=$v;
  72.             }
  73.         }
  74.         return $tables;
  75.     }
  76.     /**
  77.      * 插入数据库备份基础信息
  78.      *
  79.      * @return string
  80.      */
  81.     private function _base() {
  82.         $value = '';
  83.         $value .= '-- MySQL database dump' . $this->ds;
  84.         $value .= '-- Created by DBAction class, Power By TaoTao. ' . $this->ds;
  85.         $value .= '-- http://blog.kisscn.com ' . $this->ds;
  86.         $value .= '--' . $this->ds;
  87.         $value .= '-- 主机: ' . $this->host . $this->ds;
  88.         $value .= '-- 生成日期: ' . date ( 'Y' ) . ' 年  ' . date ( 'm' ) . ' 月 ' . date ( 'd' ) . ' 日 ' . date ( 'H:i' ) . $this->ds;
  89.         $value .= '-- MySQL版本: ' . mysql_get_server_info () . $this->ds;
  90.         $value .= '-- PHP 版本: ' . phpversion () . $this->ds;
  91.         $value .= $this->ds;
  92.         $value .= '--' . $this->ds;
  93.         $value .= '-- 数据库: `' . C("DB_NAME") . '`'. $this->ds;
  94.         $value .= '--' . $this->ds ;
  95.         $value .= '-- -------------------------------------------------------';
  96.         $value .= $this->ds . $this->ds;
  97.         return $value;
  98.     }
  99.     /**
  100.      * 插入表结构
  101.      *
  102.      * @param unknown_type $table            
  103.      * @return string
  104.      */
  105.     private function _insert_table_structure($table) {
  106.         $sql = '';
  107.         $sql .= "--" . $this->ds;
  108.         $sql .= "-- 表的结构" . $table .$this->ds."--" .$this->ds;
  109.          $M = M();
  110.         // 如果存在则删除表
  111.         $sql .= "DROP TABLE IF EXISTS `" . $table . '`' . $this->sqlEnd . $this->ds;
  112.         // 获取详细表信息
  113.         $res = $M->query ( 'SHOW CREATE TABLE `' . $table . '`' );
  114.         $sql .= $res [0]["Create Table"];
  115.         $sql .= $this->sqlEnd . $this->ds;
  116.         // 加上
  117.         $sql .= $this->ds;
  118.         $sql .= "--" . $this->ds;
  119.         $sql .= "-- 转存表中的数据 " . $table . $this->ds;
  120.         $sql .= "--" . $this->ds;
  121.         $sql .= $this->ds;
  122.         return $sql;
  123.     }

  124.     /**
  125.      * 插入语句构造
  126.      *
  127.      * @param string $table                     
  128.      * @return string
  129.      */
  130.     private function _insert_record($table) {
  131.         // sql字段逗号分割

  132.         $M=M();
  133.         $res = $M->query ( 'select * FROM `' . $table . '`' );    
  134.         // 循环每个子段下面的内容
  135.             foreach ($res as $val){
  136.         $comma = 0;
  137.                 $insert .= "INSERT INTO `" . $table . "` VALUES(";
  138.                 foreach ($val as $v){
  139.         $insert.=$comma == 0 ? "" : ",";
  140.             $insert.= ( "'" . mysql_escape_string ( $v ) . "'");
  141.                         $comma++;
  142.             
  143.             }
  144.             $insert .= ");" . $this->ds;
  145.             
  146.     }
  147.         
  148.         return $insert;
  149.     }

  150.     /**
  151.      * 写入文件
  152.      *
  153.      * @param string $sql            
  154.      * @param string $filename            
  155.      * @param string $dir            
  156.      * @return boolean
  157.      */
  158.     private function _write_file($sql, $filename, $dir) {
  159.           $dir = C("DB_BACKUP");
  160.         // 创建目录
  161.         if (! is_dir ( $dir )) {
  162.             mkdir ( $dir, 0777, true );
  163.         }
  164.         $re = true;
  165.         if (! @$fp = fopen ( $dir . $filename, "w+" )) {
  166.             $re = false;
  167.             $msg .= "打开文件失败!";
  168.         }
  169.         if (! @fwrite ( $fp, $sql )) {
  170.             $re = false;
  171.             $msg .= "写入文件失败,请文件是否可写";
  172.         }
  173.         if (! @fclose ( $fp )) {
  174.             $re = false;
  175.             $msg .= "关闭文件失败!";
  176.         }
  177.         return $re;
  178.     }
  179.     /**
  180.      * 数据库备份
  181.      * 参数:备份哪个表(可选),备份目录(可选,默认为backup),分卷大小(可选,默认2048,即2M)
  182.      *
  183.      * @param string $dir            
  184.      * @param int $size            
  185.      * @param array $tablename            
  186.      */
  187.     public function export() {
  188.         $tablename=I("table");
  189.          $dir = C("DB_BACKUP");
  190.         // 创建目录
  191.         if (! is_dir ( $dir )) {
  192.             mkdir ( $dir, 0777, true ) or die ( '创建文件夹失败' );
  193.         }
  194.         $size = C("DB_BACKUP_SIZE");
  195.         $sql = '';
  196.         $tables=explode(",", $tablename);
  197.         //print_r($tables);
  198.         $M=M();
  199.         if(!empty($tablename)){
  200.         foreach ($tables as $value) {
  201.             $msg .= '正在备份表' . $value . '<br />';
  202.             // 插入文件头信息
  203.             $sql = $this->_base ();
  204.             // 插入表结构信息
  205.             $sql .= $this->_insert_table_structure ( $value );
  206.             // 文件名前缀
  207.             $filename = date ( 'YmdHis' ) . "_" . $value;
  208.             // 分卷标识
  209.             $p = 1;
  210.             $sql .= $this->_insert_record ( $value);
  211.                         // 如果大于分卷大小,则写入文件
  212.                         //$msg.="文件大小为:".strlen ( $sql );
  213.                 if (strlen ( $sql ) >= $size * 1024) {
  214.                     $file = $filename . "_v" . $p . ".sql";
  215.                     if ($this->_write_file ( $sql, $file, $dir )) {
  216.                         $msg .= "表-" . $value . "-卷-" . $p . '<br />';
  217.                         $msg.= "-数据备份完成,生成备份文件 <span style='color:#f00;'>$dir$filename</span><br />";
  218.                     } else {
  219.                         $msg .= "备份表-" . $value . "-失败<br />";
  220.                         return false;
  221.                     }
  222.                     // 下一个分卷
  223.                     $p ++;
  224.                     // 重置$sql变量为空,重新计算该变量大小
  225.                     $sql = "";
  226.                 }else{
  227.                             // sql大小不够分卷大小
  228.             if ($sql != "") {
  229.                 $filename .=  ".sql";
  230.                 if ($this->_write_file ( $sql, $filename, $dir )) {
  231.                     $msg .= "表-" . $value . "-卷-" . $p. '<br />'. "-数据备份完成,生成备份文件 <span style='color:#f00;'>$dir$filename</span><br />";
  232.                 } else {
  233.                     $msg .= "备份卷-" . $p . "-失败<br />";
  234.                     return false;
  235.                 }
  236.             }
  237.                 }
  238.         }
  239.         }else{
  240.                     // 备份全部表
  241.             if ($tables = $M->query( "show table status from " . C("DB_NAME") )) {
  242.                 $msg .= "读取数据库结构成功!<br />";
  243.             } else {
  244.                 exit ( "读取数据库结构失败!<br />" );
  245.             }
  246.             // 插入dump信息
  247.             $sql .= $this->_base ();
  248.             // 文件名前面部分
  249.             $filename = date ( 'YmdHis' ) . "_all";
  250.             // 查出所有表
  251.             $tables = $M->query ( 'SHOW TABLES' );
  252.             // 第几分卷
  253.             $p = 1;
  254.             // 循环所有表
  255.             foreach  ( $tables as $value) {
  256.                 foreach ($value as $v) {
  257.                 
  258.                 // 获取表结构
  259.                 $sql .= $this->_insert_table_structure ( $v );
  260.                     // 单条记录
  261.                     $sql .= $this->_insert_record ( $v );
  262.                   }

  263.             }
  264.                   // 如果大于分卷大小,则写入文件
  265.                     if (strlen ( $sql ) >= $size * 1024) {

  266.                         $file = $filename . "_v" . $p . ".sql";
  267.                         // 写入文件
  268.                         if ($this->_write_file ( $sql, $file, $dir )) {
  269.                             $this->msg .= "-卷-" . $p . "-数据备份完成,生成备份文件<span style='color:#f00;'>$dir$file</span><br />";
  270.                         } else {
  271.                             $this->msg .= "备份卷-" . $p . "-失败<br />";
  272.                             return false;
  273.                         }
  274.                         // 下一个分卷
  275.                         $p ++;
  276.                         // 重置$sql变量为空,重新计算该变量大小
  277.                         //$sql = "";
  278.                     }
  279.                 // sql大小不够分卷大小
  280.             if ($sql != "") {
  281.                 $filename .=  ".sql";
  282.                 if ($this->_write_file ( $sql, $filename, $dir )) {
  283.                     $msg .= "数据库-" . C("DB_NAME") . "-卷-" . $p. '<br />'. "-数据备份完成,生成备份文件 <span style='color:#f00;'>$dir$filename</span><br />";
  284.                 } else {
  285.                     $msg .= "备份卷-" . $p . "-失败<br />";
  286.                     return false;
  287.                 }
  288.             }
  289.         }
  290.         echo $msg; 
  291.     }

  292.     /**
  293.      * 导入备份数据
  294.      * 说明:分卷文件格式20120516211738_all_v1.sql
  295.      * 参数:文件路径(必填)
  296.      *
  297.      * @param string $sqlfile            
  298.      */
  299.     function restore($file) {
  300.         $sqlfile=C("DB_BACKUP").$file;

  301.         // 检测文件是否存在
  302.         if (! file_exists ( $sqlfile )) {
  303.             exit ( "文件不存在!请检查" );
  304.         }
  305.         $this->lock ( C("DB_NAME") );
  306.         // 获取数据库存储位置
  307.         $sqlpath = pathinfo ( $sqlfile );
  308.     //    $this->sqldir = $sqlpath ['dirname'];
  309.         // 检测是否包含分卷,将类似20120516211738_all_v1.sql从_v分开,有则说明有分卷
  310.         $volume = explode ( "_v", $sqlfile );
  311.         $volume_path = $volume [0];
  312.         $msg .= "请勿刷新及关闭浏览器以防止程序被中止,如有不慎!将导致数据库结构受损<br />";
  313.         $msg .= "正在导入备份数据,请稍等!<br />";
  314.         if (empty ( $volume [1] )) {
  315.              $msg .= "正在导入sql:<span style='color:#f00;'>" . $sqlfile . '</span><br />';
  316.             // 没有分卷
  317.             if ($this->_import ( $sqlfile )) {
  318.             echo    $msg .= "数据库导入成功!";
  319.         //$this->success("数据库导入成功!");
  320.             } else {
  321.                 exit ( '数据库导入失败!' );
  322.             }
  323.         } else {
  324.             // 存在分卷,则获取当前是第几分卷,循环执行余下分卷
  325.             $volume_id = explode ( ".sq", $volume [1] );
  326.             // 当前分卷为$volume_id
  327.             $volume_id = intval ( $volume_id [0] );
  328.             while ( $volume_id ) {
  329.                 $tmpfile = $volume_path . "_v" . $volume_id . ".sql";
  330.                 // 存在其他分卷,继续执行
  331.                 if (file_exists ( $tmpfile )) {
  332.                     // 执行导入方法
  333.                     $msg .= "正在导入分卷 $volume_id :<span style='color:#f00;'>" . $tmpfile . '</span><br />';
  334.                     if ($this->_import ( $tmpfile )) {

  335.                     } else {
  336.                         $volume_id = $volume_id ? $volume_id :1;
  337.                         exit ( "导入分卷:<span style='color:#f00;'>" . $tmpfile . '</span>失败!可能是数据库结构已损坏!请尝试从分卷1开始导入' );
  338.                     }
  339.                 } else {
  340.                     $msg .= "此分卷备份全部导入成功!<br />";
  341.                     echo $msg;
  342.                 }
  343.                 $volume_id ++;
  344.             }
  345.         }
  346.     }
  347.     /**
  348.      * 将sql导入到数据库(普通导入)
  349.      *
  350.      * @param string $sqlfile            
  351.      * @return boolean
  352.      */
  353.     private function _import($sqlfile) {
  354.         // sql文件包含的sql语句数组
  355.         $sqls = array ();
  356.         $f = fopen ( $sqlfile, "rb" );
  357.         // 创建表缓冲变量
  358.         $create_table = '';
  359.         while ( ! feof ( $f ) ) {
  360.             // 读取每一行sql
  361.             $line = fgets ( $f );
  362.             // 这一步为了将创建表合成完整的sql语句
  363.             // 如果结尾没有包含';'(即为一个完整的sql语句,这里是插入语句),并且不包含'ENGINE='(即创建表的最后一句)
  364.             if (! preg_match ( '/;/', $line ) || preg_match ( '/ENGINE=/', $line )) {
  365.                 // 将本次sql语句与创建表sql连接存起来
  366.                 $create_table .= $line;
  367.                 // 如果包含了创建表的最后一句
  368.                 if (preg_match ( '/ENGINE=/', $create_table)) {
  369.                     //执行sql语句创建表
  370.                     $this->_insert_into($create_table);
  371.                     // 清空当前,准备下一个表的创建
  372.                     $create_table = '';
  373.                 }
  374.                 // 跳过本次
  375.                 continue;
  376.             }
  377.             //执行sql语句
  378.             $this->_insert_into($line);
  379.         }
  380.         fclose ( $f );
  381.         return true;
  382.     }
  383.     //插入单条sql语句
  384.     private function _insert_into($sql){
  385.         $M=M();
  386.         if (! $M->query( trim ( $sql ) )) {
  387.             $msg .= mysql_error ();
  388.             return false;
  389.         }
  390.     }
  391.         // 锁定数据库,以免备份或导入时出错
  392.     private function lock($tablename, $op = "WRITE") {
  393.         $M=M();
  394.         if ($M->query( "lock tables " . $tablename . " " . $op ))
  395.             return true;
  396.         else
  397.             return false;
  398.     }

  399.     // 解锁
  400.     private function unlock() {
  401.         $M=M();
  402.         if ($M->query ( "unlock tables" ))
  403.             return true;
  404.         else
  405.             return false;
  406.     }
  407.     
  408. }
  409. /**
  410.  +----------------------------------------------------------
  411.  * 功能:计算文件大小
  412.  +----------------------------------------------------------
  413.  * @param int $bytes
  414.  +----------------------------------------------------------
  415.  * @return string 转换后的字符串
  416.  +----------------------------------------------------------
  417.  */
  418. function byteFormat($bytes) {
  419.     $sizetext = array(" B", " KB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB");
  420.     return round($bytes / pow(1024, ($i = floor(log($bytes, 1024)))), 2) . $sizetext[$i];
  421. }    
备份效果: