`

实现简单的 DB 迁移管理

php 
阅读更多
<?php
Core_Autoloader::loadFile(COREPATH . '/vendor/SingleTableCRUD.class.php',true);

/**
 * 迁移操作入口
 *
 * @package pkg
 *
 */
class Pkg_Gen_Table_Migration {

	private static $migrationTable = 'sql_table_migration';

	/**
	 * @var Pkg_Gen_Table_MigrationLog
	 */
	static $logger = null;
	
	/**
	 * @return TplEngine
	 */
	private static function getTplEngine(){
		static $tplEngine = null;
		if (!$tplEngine){
			Core_Autoloader::loadFile(COREPATH . '/vendor/TplEngine.class.php');

			$tplConfig = array(
				'templateDir' => dirname(__FILE__) . '/_views',
				'enableCache' => false,
			);
			$tplEngine = new TplEngine($tplConfig);
		}
		return $tplEngine;
	}

	private static function getMigrations($migrationDir,$tableClassPrefix){
		static $migrations = null;
		if ($migrations) return $migrations;

		$migrations = array();
		$index = 1;
		// 获取迁移类对象
		foreach (glob("{$migrationDir}/*.php") as $filename) {
			$id = basename($filename,'.php');
			$className = "{$tableClassPrefix}{$id}";
			// 加载迁移类到系统
			Core_Autoloader::loadClass($className);

			$obj = new $className();
			// 校验迁移类是否实现了Pkg_Gen_Table_MigrationElement接口
			if ( !($obj instanceof Pkg_Gen_Table_MigrationElement) ){
				throw new Core_Exception_TypeMismatch('迁移类对象','Pkg_Gen_Table_MigrationElement',$className);
			}

			$migrations[$index] = array('id' =>$id,'class' => $className ,'instance' => $obj);
			$index ++;
		}

		return $migrations;
	}

	private static function initMigrationTable(Core_DB $dbo){
		static $is = false;
		if (!$is){
			$row = $dbo->getRow( sprintf("SHOW TABLES LIKE '%s'",self::$migrationTable) );
			if (!empty($row)){
				$is = true;
				return;
			}
			$tb = Pkg_Gen_Table_DML::newInstance($dbo,self::$migrationTable);

			$tb->struct(array(
				$tb->combindColumnParams('version','int',true,6),
			))
			->setPrimaryKey('version')
			->setOptions(array(
				Pkg_Gen_Table_DML::ENGINE => Pkg_Gen_Table_DML::ENGINE_INNODB,

			))->create();

			$tb->execute();
			$is = SingleTableCRUD::insert(self::$migrationTable,array('version'=>0));
			self::$logger->append($dbo->lastsql);
		}
	}

	static function ls(Core_DB $dbo,$migrationDir,$tableClassPrefix,$saveUrl){

		if ( !(is_readable($migrationDir) && is_dir($migrationDir)) )
			throw new Exception("无效的迁移类文件存放路径: {$migrationDir}");

		self::initMigrationTable($dbo);
		$migrations = self::getMigrations($migrationDir,$tableClassPrefix);

		// 得到当前版本号,缺省为0
		$curversion = (int) $dbo->getOne(sprintf('select version from %s',self::$migrationTable));

		self::getTplEngine()->assign('database',$dbo->getDSN('database'));
		self::getTplEngine()->assign('migrations',$migrations);
		self::getTplEngine()->assign('version',$curversion);
		self::getTplEngine()->assign('saveurl',$saveUrl);
		self::getTplEngine()->display('migrations.php');
	}

	static function change(Core_DB $dbo,$migrationDir,$tableClassPrefix,$newversion,$lastversion){

		if ( !(is_readable($migrationDir) && is_dir($migrationDir)) )
			throw new Exception("无效的迁移类文件存放路径: {$migrationDir}");

		self::initMigrationTable($dbo);
		$migrations = self::getMigrations($migrationDir,$tableClassPrefix);

		// 得到当前版本号,缺省为0
		$curversion = (int) $dbo->getOne(sprintf('select version from %s',self::$migrationTable));

		if ($curversion != $lastversion) throw new Exception("无效的参数 lastversion: {$lastversion}");

		if ($curversion == $newversion) throw new Exception("版本无需迁移操作");
		
		if ($newversion > 0){
			if (!isset($migrations[$newversion])) throw new Exception("无效的参数 newversion: {$newversion}");
		}
		
		// 开始进行版本迁移操作
		if ($curversion > $newversion){
			// 反向
			for($start=$curversion,$end = $newversion; $start > $end; $start --){
				$instance = $migrations[$start]['instance'];
				/* @var $instance Pkg_Gen_Table_MigrationElement */
				self::$logger->append($migrations[$start]['class'] . '::down()');
				try {
					$instance->down();
				} catch( Exception $ex){
					throw new Exception("反向迁移: {$curversion}到{$newversion}失败,请修正后再操作... 可参考迁移日志");
				}
				$dbo->startTrans();
				$is = SingleTableCRUD::incrField(self::$migrationTable,null,'version',-1);
				self::$logger->append($dbo->lastsql);
				$dbo->completeTrans($is);
				
				if (!$is) throw new Exception("反向迁移: {$curversion}到{$newversion}失败,请修正后再操作... 可参考迁移日志");
			}
			
		}else {
			// 正向
			for($start=$curversion + 1,$end = $newversion + 1; $start < $end; $start ++){
				$instance = $migrations[$start]['instance'];
				/* @var $instance Pkg_Gen_Table_MigrationElement */
				self::$logger->append($migrations[$start]['class'] . '::up()');
				try {
					$instance->up();
				} catch( Exception $ex){
					throw new Exception("正向迁移: {$curversion}到{$newversion}失败,请修正后再操作... 可参考迁移日志");
				}
				
				$dbo->startTrans();
				$is = SingleTableCRUD::incrField(self::$migrationTable,null,'version',1);
				self::$logger->append($dbo->lastsql);
				$dbo->completeTrans($is);
				
				if (!$is) throw new Exception("正向迁移: {$curversion}到{$newversion}失败,请修正后再操作... 可参考迁移日志");
			}
			
		}
	
		
	}

}

/**
 * 迁移元素接口
 *
 * @package pkg
 *
 */
interface Pkg_Gen_Table_MigrationElement {

	/**
	 * 正向迁移操作
	 *
	 * @return bool
	 */
	function up();

	/**
	 * 逆向此次迁移操作
	 *
	 * @return bool
	 */
	function down();

	/**
	 * 迁移操作的说明
	 *
	 * @return string
	 */
	function description();
}

/**
 * 迁移日志类
 *
 * @package pkg
 *
 */
class Pkg_Gen_Table_MigrationLog extends Core_LogWriterAbstract {

	/**
	 * 保存运行期间的日志
	 *
	 * @var string
	 */
	private $_log = '';

	/**
	 * 日期格式
	 *
	 * @var string
	 */
	private $dateFormat = 'Y-m-d H:i:s';

	/**
	 * 保存日志的文件名
	 *
	 * @var string
	 */
	private $_logFilename = '';

	function __construct($logDir){
		if ( !(is_writable($logDir) && is_dir($logDir)) ){
			throw new Exception("无效的迁移日志文件存放路径: {$logDir}");
		}
	
        $logDir = realpath($logDir);
        if (substr($logDir, -1) != DIRECTORY_SEPARATOR) {
            $logDir .= DIRECTORY_SEPARATOR;
        }
        
		$this->_logFilename = $logDir . 'sql_table_migration.txt';
		unset($logDir);
		$app_start_time = Core_App::ini('+app_start_time+');
		$sec = (int) $app_start_time;
		$usec = $app_start_time - $sec;

		$this->_startTag = sprintf("[%s %s] ======= IWP Migration Loaded =======\n",
		date($this->dateFormat, $sec), $usec);
		
		// 注册脚本结束时要运行的方法,将缓存的日志内容写入文件
		Core_Halt::getInstance()->add(array($this, '__writeLog'));
	}

	function append($msg, $title = '', $level = 'info'){
		if (empty($msg)) return;
		$this->_log .= sprintf("[%s] %s\n", date($this->dateFormat), print_r($msg, true));
	}

	/**
	 * 将缓存的日志信息写入实际存储,并清空缓存
	 * 此方法由系统自动调用
	 *
	 */
	function __writeLog(){
		if (empty($this->_log)) return;
		$app_start_time = Core_App::ini('+app_start_time+');

		$shutdown_time = microtime(true);
		$sec = (int) $shutdown_time;
		$usec = $shutdown_time - $sec;

		$elapsedTime = $shutdown_time - $app_start_time;

		$content = $this->_startTag . $this->_log . sprintf("[%s %s] ======= IWP Migration End (elapsed: %f seconds) =======\n\n",date($this->dateFormat, $sec), $usec, $elapsedTime);

		$fp = fopen($this->_logFilename, 'a');
		if (!$fp) { return; }
		flock($fp, LOCK_EX);
		fwrite($fp, str_replace("\r", '', $content));
		flock($fp, LOCK_UN);
		fclose($fp);
	}
}
 
分享到:
评论
3 楼 vb2005xu 2012-04-09  
http://www.infoq.com/cn/infoq.action?newsidx=1220
2 楼 vb2005xu 2012-04-09  
http://www.eclipse.org/orion/getstarted.php
1 楼 vb2005xu 2012-04-04  
mysql 授权管理
引用

最近学习PHP,装了个phpwind论坛和FTP流量插件,需要远程连接MySQL数据库.不知道如何打开本地服务器的远程连接.现在本地服务器上的论坛和FTP流量插件都运行正常,在另一台服务器上安装插件,连不上数据库.

    服务器信息
    PHP程式版本: 4.3.11
    MySQL 版本: 4.1.10-nt
    服务器端信息: Microsoft-IIS/5.0
    装有phpMyAdmin

    A1:

    远程连接到MySQL需要做的

    1. 进入MySQL,创建一个新用户xuys:
  
    格式: grant 权限 on 数据库名.表名 用户@登录主机 identified by "用户密码";
    grant select,update,insert,delete on *.* to xuys@192.168.88.234 identified by "xuys1234";
  
    查看结果,执行:
    use mysql;
    select host,user,password from user;
  
    可以看到在user表中已有刚才创建的xuys用户,host字段表示登录的主机,其值可以用IP,也可用主机名,将host字段的值改为%就表示在任何客户端机器上能以xuys用户登录到MySQL服务器,建议在开发时设为%.
    update user set host = '%' where user = 'xuys';

    2.
    ./mysqladmin -u root -p pwd reload
    ./mysqladmin -u root -p pwd shutdown

    3.
    ./mysqld_safe --user=root &
  
    记住: 对授权表的任何修改都需要重新reload,即执行第3步.

    如果经过以上3个步骤还是无法从客户端连接,请执行以下操作,在MySQL数据库的db表中插入一条记录:
    use mysql;
    insert into db values('192.168.88.234','%','xuys','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y');
    update db set host = '%' where user = 'xuys';
  
    重复执行上面的第2,3步.


    A2:

    Web与MySQL数据库分离开来是一个不错的选择,避免因为大量的数据库查询占用CPU而使Web资源不足,同时可以使Web服务器的资源尽最大的提供浏览服务,而数据库服务器单独的只处理数据库事务.

    我对这方面的原理不甚太十分了解,我的做法其实就是下面要说的,很简单.大家有更好的经验和技巧不妨提出来分享一下.

    适用范围: 拥有独立主机权限
    硬件配置: 两台服务器,至于具体服务器硬件配置就不在本文范围内了
    其中: A为Web服务器(假设IP为: 192.192.192.192),B为MySQL数据服务器(假设IP为: 168.168.168.168)

    着手动作:
    1. 在Web服务器A配置好Web服务.关于这方面文章很多了.假设Web服务器的IP为: 192.192.192.192
    2. 在数据库服务器B安装好MySQL服务
    3. 现在新版的MySQL一般默认都不允许远程连接的,需要建立远程连接账号才可以
  
    以命令行方式使用root账号进入MySQL
    mysql -u root -p pass

    选择进入MySQL数据库
    use mysql;

    查看所有存在的账号和地址
    SELECT `Host`,`User` FROM `user`;

    比如我的就是:

    +------------+-------+
    | Host        | User  |
    +------------+-------+
    | localhost |          |
    | localhost | pma  |
    | localhost | root   |
    +------------+-------+
    3 rows in set (0.00 sec)
  
    也就是说,存在三个只允许本地连接的(localhost)账号,分别为root,pma,空用户.

    现在决定让root具有上面那个Web服务器A的远程链接的权限,那么就这样:
    UPDATE `user` SET `Host` = '192.192.192.192' WHERE `User` = 'root' LIMIT 1;

    这样192.192.192.192这台Web服务器就可以远程连接到这个数据库服务器了,假如你想让任何远程机器都可以连接这个数据库,就将192.192.192.192换为%,不过不建议这样做,原因你知道啦!

    假如你想新建一个用户new_user具备远程链接的权限的话,就这样:
    INSERT INTO `user` ( `Host` , `User` , `Password` , `Select_priv` , `Insert_priv` , `Update_priv` , `Delete_priv` , `Create_priv` , `Drop_priv` , `Reload_priv` , `Shutdown_priv` , `Process_priv` , `File_priv` , `Grant_priv` , `References_priv` , `Index_priv` , `Alter_priv` , `Show_db_priv` , `Super_priv` , `Create_tmp_table_priv` , `Lock_tables_priv` , `Execute_priv` , `Repl_slave_priv` , `Repl_client_priv` , `ssl_type` , `ssl_cipher` , `x509_issuer` , `x509_subject` , `max_questions` , `max_updates` , `max_connections` ) VALUES ('192.192.192.192', 'new_user', PASSWORD( 'new_user_password' ) , 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', '', '', '', '', '0', '0', '0');

    将new_user改为你想要的名字就可以了,密码是: new_user_password,当然你可以随意设置.

    当你的数据库可以远程连接后,你就可以在你的Web服务器的论坛config.inc.php中设置$dbhost变量为你的MySQL数据库服务器B的IP了:
    $dbhost = '168.168.168.168';

    实际操作中,最好两台机器在同一个机房的同一网段/防火墙内.当然如果有可能的话,将数据库服务器放置于Web服务器网络内的局域网中就更好了.


    Q3:

    还是这样简洁些:
    grant all on yourdb.* to yourUsername@yourHost identified by "yourPassword";
    flush privileges;     //使权限立刻生效


引用

解决Mysql无法远程连接的问题

1、Mysql的端口是否正确,通过netstat -ntlp查看端口占用情况,一般情况下端口是3306。在用工具连接MySQl是要用到端口。例如My Admin\My Query Browser\MySQl Front等。

2、检查用户权限是否正确。
   例如:用户Tester,user表里有两条记录:host分别为localhost和%(为了安全,%可以换成你需要外部连接的IP)。

3、查看/etc/my.cnf中,skip-networking 是否已被注掉,需要注掉。
   报错:ERROR 2003 (HY000): Can't connect to MySQL server on '192.168.51.112' (111)

4、查看iptables是否停掉,没关的情况下,无法连接。
   通过:service iptables stop临时关闭。
   报错:ERROR 2003 (HY000): Can't connect to MySQL server on '192.168.51.112' (113)

相关推荐

    datax数据迁移插件-db2reader

    其设计理念是通过简单的配置,实现复杂的数据同步任务。DB2Reader作为DataX的一部分,专门负责从IBM的DB2数据库中读取数据,为数据迁移提供便利。 二、DB2Reader工作原理 DB2Reader主要通过JDBC(Java Database ...

    IBM Portal 6.1数据库迁移与管理.pdf

    ### IBM Portal 6.1 数据库迁移与管理 #### 一、概述 IBM Portal 6.1 是一款功能强大的企业级门户解决方案...通过遵循本文档中介绍的步骤和最佳实践,可以有效地实现数据库的迁移,并确保迁移后的系统能够稳定运行。

    Go-Go数据库迁移工具

    `goose`是一个专为Go语言开发的数据库迁移工具,它提供了一种方便的方式来管理和执行数据库的迁移,使得开发者可以以有序、可控的方式更新数据库的结构和数据。本文将深入探讨`goose`的功能、用法以及其在Go开发中的...

    DBServer--DB2数据连接工具

    无论是进行跨数据库的数据迁移,还是进行多数据库的同步,都能在DBServer--DB2数据连接工具中实现。 在提供的压缩包子文件中,"dbeaver-2.3.3-x86_64-setup.exe"是该工具的安装程序,适用于64位操作系统。这意味着...

    MySQL向Sybase ASE迁移指南

    Sybase ASE的日志管理与MySQL有所不同,支持多种日志模式,如完整日志、简单日志等。 ##### 4.3 数据库服务器配置 Sybase ASE允许对服务器进行详细的配置,包括内存分配、CPU使用等。 ##### 4.4 设置数据库选项 ...

    为Oracle DBA准备的DB2数据库管理

    随着企业需求的不断变化和技术的进步,跨平台管理和迁移变得越来越常见。Oracle和IBM DB2作为市场上两大主流的关系型数据库管理系统(RDBMS),各自拥有独特的架构、特性和管理方式。本文旨在探讨这两种数据库系统的...

    TD迁移步骤记录

    项目文件的迁移相对简单,但同样需要仔细操作: 1. **拷贝项目文件**:将备份的“TD_Dir”目录内的有效域和有效项目文件拷贝到目标主机“B”的相应目录下。 2. **修改项目配置文件**:“Dbid.ini”文件位于每个...

    MySQL to DB2 UDB Conversion Guide

    - **db2move**: 实现数据和模式的迁移。 **数据库访问** DB2 UDB支持多种访问方式: - **ODBC/JDBC**: 为大多数编程语言提供了标准接口。 - **CLI**: 提供命令行接口,适用于脚本编写。 ##### 1.2 MySQL数据库 **...

    神通数据库 DBstudio

    神通数据库 DBstudio 是一款专为神通数据库设计的集成开发环境,它提供了全面的数据库管理和开发功能,方便用户进行数据...通过深入理解和熟练运用DBstudio,用户可以在神通数据库环境中实现高效的数据管理与应用开发。

    DB4O,让文本文档做数据库的东东

    在“让文本文档做数据库的东东”这个主题中,我们将深入探讨DB4O如何通过文本文档实现数据存储,以及这种存储方式的优势和应用场景。 DB4O的工作原理是将Java或.NET中的对象序列化为字节流,然后将其存储在一个二...

    DBImport工具

    总的来说,DBImport是一个强大且实用的数据库管理和转换工具,无论是在日常开发、测试,还是在数据库迁移和数据格式转换等场景下,都能大大简化工作流程,提升数据处理的效率。对于任何涉及多个数据库系统和数据格式...

    db2数据导出工具

    DB2数据导出工具是DB2数据库管理中的一个强大工具,它的灵活性和易用性使得数据导出变得简单。无论是进行数据迁移、备份,还是与其他系统集成,`db2export`都提供了一个高效且可控的解决方案。正确理解和掌握这个...

    Microsoft OLE DB Provider for DB2 Version 2.0

    Microsoft OLE DB ...开发者可以利用这个工具实现各种复杂的数据操作,包括数据迁移、同步、分析等,从而提升应用的性能和功能。在实际应用中,熟悉和掌握这个组件的使用,将对提升数据库管理和开发能力大有裨益。

    一个好用的数据库迁移工具(Database Publishing)

    可将数据结构及数据导出为SQL语句,从而实现不同版本(SQL2000、SQL2005、SQL2008等)以及SQL数据库与其它数据库之间的迁移,操作方法简单,功能灵活,是一款不错的数据库管理与维护工具。 Database Publishing...

    OPC2DB.zip

    通过OPC2DB,用户无需深入理解数据库技术,只需使用ODBC(Open Database Connectivity)即可轻松实现数据迁移。 ODBC是微软提供的一种数据库访问接口,它允许应用程序通过统一的方式与各种不同类型的数据库进行交互...

    一个关于DB2数据库学习文档集

    这些实例可能涵盖了从简单的数据插入到复杂的业务逻辑实现的全过程。 数据转换文档则是关于如何在不同格式或系统间迁移和转换数据的指南。在实际工作中,数据往往需要在不同系统间流动,比如从CSV文件导入DB2,或者...

    OrientDB中文使用手册

    - **关系型数据库迁移**:指导如何从关系型数据库迁移到OrientDB。 - **备份与恢复**:详细介绍备份策略和恢复流程。 #### 九、OrientDB工具集 - **控制台工具**:用于执行日常管理和维护任务。 - **Web工具集**:...

    Oracle to DB2 Conversion Guide for Linux, UNIX, and Windows

    通过本篇内容的详细介绍,我们了解到Oracle到DB2的转换不仅仅是一项简单的技术任务,而是一个涉及多个层面复杂工作的系统工程。从前期的评估准备到后期的实施和切换,每一步都需要仔细规划和周密部署。希望本文能为...

    在VC6中利用动态SQL实现对DB2访问.pdf

    在数据管理和存储领域,DB2通用数据库管理系统作为一种高效、强大的工具,广泛应用于各类企业级应用中。DB2允许用户利用结构化查询语言(SQL)来创建、修改、查询和管理数据。然而,在实际应用中,特别是在大中型...

    GaussDB 200 6.5.1 软件安装 02.pdf

    GaussDB是一款由华为技术有限公司研发的分布式关系型数据库管理系统,适用于处理海量数据的高并发业务场景。 ### 1. GaussDB 200 6.5.1版本安装概览 首先,GaussDB 200 6.5.1版本的软件安装文档详细介绍了不同安装...

Global site tag (gtag.js) - Google Analytics