实现简单的 DB 迁移管理
<?phpCore_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);}}? 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)
2 楼 vb2005xu 2012-04-09 http://www.eclipse.org/orion/getstarted.php 3 楼 vb2005xu 2012-04-09 http://www.infoq.com/cn/infoq.action?newsidx=1220