`

实现简单的 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)

相关推荐

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

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

    [Python+Django]Web学生信息管理系统毕业设计之数据库及系统实现源码

    本资源基于Python + Django实现学生信息管理系统的源码。 简单的包装下毕设应该没问题了。 使用方法: 1、下载本资源并解压 2、在项目文件夹下输入命令:pip install -r requirements.txt 安装相关包 3、在settings....

    Oracle数据库管理员技术指南

    4.9 保护 SYSTEM 表空间的三种简单 方法 4.10 为什么必须保护 ORACLE_HOME 4.11 保护操作系统 4.12 如何保护回退段 4.13 分类和划分数据 4.14 划分表空间的优先次序 4.15 如何配置高可用性的 TEMP 表空间 ...

    关于应急管理中地理空间大数据的管理:一些观点

    我们可以简单地将它们分为两种:在分布式数据库上扩展或迁移到大数据存储系统。以前,他们大多采用基于大规模并行处理(MPP)的体系结构,在该体系结构中,数据在一组独立的节点中进行存储和检索。每个节点都可以视...

    ABP(2.02)框架相关Demo代码以及报错处理和中文说明手册

    提供Repository仓储模式支持不同的ORM(已实现Entity Framework 、NHibernate、MangoDb和内存数据库) 支持并实现数据库迁移(EF 的 Code first) 模块化开发(每个模块有独立的EF DbContext,可单独指定数据库) ...

    基于J2EE的B2C电子商务系统开发

    在软件工程思想的指导下,运用J2EE开发工具IBM的开发环境 WebSphere,遵循J2EE的相关规范,与同...在某些情况下,使用 JDK 1.4 编译器重新编译应用程序代码将是解决迁移相关问题的简单方法。然而,在极少的情况下,将需

    flask-blog:一个基于Flask的多人blog网站

    截图主页主题文章文章列表新文章用户主页消息问答问题后台管理如何使用创建数据库#项目使用Flask-Migrate扩展, 用来管理数据库#创建迁移仓库$ python manage.py db init#自动创建迁移脚本$ python manage.py db ...

    fastapi-crud:使用FastAPI框架进行的简单CRUD

    它可以用作非常基本的CRUD样板,它与PostgreSQL数据库一起完全码头化,使用Tortoise作为db ORM以及Aerich进行迁移,具有Pytest,Codecoverage和Black支持,并使用Poetry作为依赖项管理器。 可在上获得测试环境很难...

    章鱼:ActiveRecord的数据库分片

    尽管有多个项目实现了分片(例如DbCharmer,DataFabric,MultiDb),但每个项目都有其自身的局限性。 章鱼项目的主要目标是提供一种更好的数据库分片方法。 功能列表: 该API设计为尽可能简单。 Octopus专注于最终...

    员工管理系统:从一个地方管理您的员工。 配备员工休假管理:beach_with_umbrella:,工资单:dollar_banknote:生成和电子邮件,消息传递:incoming_envelope:等等! 用:red_heart_selector:和Laravel一起构建

    员工管理系统这个小伙伴可以帮助您管理员工数据库! 内置 :sparkling_heart: 与Laravel #产品特点1员工管理/数据库...配置完成后,运行:php artisan迁移运行:php artisan db:seed管理员/职员示例用户:检查种子文件

    AstronacciDatabaseSystem:该网站将帮助Astronacci International工人的日常工作和管理客户数据库

    我们认为,发展必须是一种令人愉快的,富有创造力的经历,才能真正实现。 Laravel试图通过减轻大多数Web项目中使用的常见任务来减轻开发工作的痛苦,例如: 。 。 用于和存储的多个后端。 富有表现力,直观的 ...

    redmine_datacenter:未维护 - 此插件可帮助您管理(小型)数据中心

    如果你怀疑你把它放在了很好的水平,你可以检查你有一个 vendor/plugins/redmine_datacenter/init.rb 文件使用 rake db:migrate_plugins 运行迁移重新启动您的 Redmine 实例(取决于您的托管方式)贡献如果你喜欢这...

    crmproject:CRM项目-Laravel

    简单的Elasticsearch实现。 RestApi和Web界面。 Rest Api文档: 验证用户 方法-开机自检: URI- /api/authenticate – params- email, password 描述–对用户进行身份验证并创建jtw。 用户注册用户: 方法-开机...

    云计算第二版

    3.5 简单数据库服务Simple DB 109 3.5.1 重要概念 110 3.5.2 存在的问题及解决办法 112 3.5.3 Simple DB和其他AWS的结合使用 112 3.6 关系数据库服务RDS 113 3.6.1 SQL和NoSQL数据库的对比 113 3.6.2 RDS数据库原理 ...

    dbproxy]基于阿里cobar增强的opencloudb

    OpenCloudb介绍 什么是OpenCloudb?简单的说,OpenCloudb...• 实现更全面的监控管理功能 • 与HDFS集成,提供SQL命令,将数据库装入HDFS中并能够快速分析 • 集成优秀的开源报表工具,使之具备一定的数据分析的能力

    使用Subversion进行版本控制(针对 Subversion 1.4)

    迁移CVS版本库到Subversion C. WebDAV和自动版本 什么是WebDAV ? 自动版本化 客户端交互性 独立的 WebDAV 应用程序 Microsoft Office,Dreamweaver,Photoshop Cadaver,DAV 浏览器 文件浏览器 WebDAV 扩展 ...

    MySQL5 权威指南第3版中文版_part1

     22.5 mysql_install_db脚本(安装mysql数据库)  22.6 mysql_fix_privileges脚本(更新mysql数据库)  22.7 mysql_fix_extensions脚本(重命名MyISAM文件)  22.8 mysql程序(SQL命令解释器)  22.9 ...

    Oracle数据库实验操作

    实验138:db_cache命中率和db_cache的细化管理 224 实验139: v$latch的使用 225 实验140:log_buffer的优化 227 实验141:pga的优化 227 不同的存储格式 229 实验142:OMF管理的文件 229 实验143:处理行迁移 230 ...

    MySQL 5权威指南(第3版) 中文版 下载地址

     22.5 mysql_install_db脚本(安装mysql数据库)  22.6 mysql_fix_privileges脚本(更新mysql数据库)  22.7 mysql_fix_extensions脚本(重命名MyISAM文件)  22.8 mysql程序(SQL命令解释器)  22.9 ...

Global site tag (gtag.js) - Google Analytics