问题描述
|
我有一个不断增长的PHP应用程序。该数据库曾经位于单个主数据库上,但是我们打算通过相当标准的主/从复制来更改数据库,以提高性能和HA。
由于这个应用程序是大量读取的,因此我希望将读取委派给从属副本,并将写入交给主副本。
该应用程序基于Zend Framework 1.1.10,并使用Zend_Db。
我最想让这个应用程序拆分读写操作而不过多重构代码的最佳策略是什么? (我意识到这里可能涉及一些重构)。
附言:
我看过MysqL Proxy,它似乎可以坐在数据库服务器和应用程序之间透明地拆分读写,但是我不确定在生产环境中使用它时的性能问题。有人对此有经验吗?
解决方法
正如您所说的,MySQlProxy可以是一个解决方案,但我个人从未在生产中对其进行过测试。
我在代码中使用2个Db连接来拆分写入和读取请求。 80%的常规任务是通过读取连接完成的。您可以使用Zend_Application_Resource_Multidb来处理此问题(对我来说,我很早以前就完成了这一部分,我只是在注册表中存储了第二个Db连接)。
首先将您的用户权限限制为
读取操作并创建另一个数据库
具有写授权的用户。
然后跟踪每个写入请求
您的代码(\“更新\”,\“插入\”,
\“删除\”是一个不错的开始),然后尝试
专门拨打所有这些电话
帮手。
运行您的应用程序并观察其崩溃,然后解决问题:-)
一开始就认为这个问题比较容易。例如:
我通常有一个Zend_Db_Table工厂,带有一个\'read \'或\'write \'参数,并给我一个正确的Zend_Db_Table的Singleton(一个双重Singleton,它可以有一个读取实例和一个写入实例)。然后,我只需要确保在使用写访问查询/操作时使用正确的初始化的Zend_Db_Table。请注意,将Zend_Db_Table用作单例时,内存使用情况要好得多。
我尝试在TransactionHandler中获取所有写操作。我在那里可以检查是否仅使用与正确连接链接的对象。然后在控制器上管理事务,但我从不尝试在数据库层中管理事务,所有启动/提交/回滚的想法都在控制器(或其他概念层,但不在DAO层)上完成。
最后一点,交易很重要。如果要管理事务,请使用启用了WRITE的连接在事务内部发出READ请求,这一点很重要。由于在事务处理之前完成的所有读取都应被视为已过时,并且如果您的数据库后端正在执行隐式锁,则必须发出读取请求才能获取锁。如果您的数据库后端未执行隐式读取,那么您还必须在事务中执行行锁。这意味着您不应该依赖SELECT关键字在只读连接上推送该请求。
如果您的应用程序中使用了很好的数据库层,则更改并不难。如果您对数据库/ DAO层进行了混乱处理,则可能会更困难。
, h2。曾德
我只是修补了Zend PDO_MYSQL以分离读写连接。
为此,您只需要在应用程序配置中指定其他参数:
\'databases\' => array (
\'gtf\' => array(
\'adapter\' => \'PDO_MYSQL\',\'params\' => array(
\'host\' => \'read.com\',\'host_write\' => \'write-database-host.com\',\'dbname\' => \'database\',\'username\' => \'reader\',\'password\' => \'reader\',\'username_write\' => \'writer\',\'password_write\' => \'writer\',\'charset\' => \'utf8\'
)
),
这里所有的“ SELECT ... \”查询都将使用主机。
所有其他查询将使用* host_write *。
如果未指定host_write,则所有查询都使用host。
补丁:
diff --git a/Modules/Tools/Externals/Zend/Db/Adapter/Abstract.php b/Modules/Tools/Externals/Zend/Db/Adapter/Abstract.php
index 5ed3283..d6fccd6 100644
--- a/Modules/Tools/Externals/Zend/Db/Adapter/Abstract.php
+++ b/Modules/Tools/Externals/Zend/Db/Adapter/Abstract.php
@@ -85,6 +85,14 @@ abstract class Zend_Db_Adapter_Abstract
* @var object|resource|null
*/
protected $_connection = null;
+
+
+ /**
+ * Database connection
+ *
+ * @var object|resource|null
+ */
+ protected $_connection_write = null;
/**
* Specifies the case of column names retrieved in queries
@@ -299,10 +307,13 @@ abstract class Zend_Db_Adapter_Abstract
*
* @return object|resource|null
*/
- public function getConnection()
+ public function getConnection($read_only_connection = true)
{
$this->_connect();
- return $this->_connection;
+ if (!$read_only_connection && $this->_connection_write)
+ return $this->_connection_write;
+ else
+ return $this->_connection;
}
/**
diff --git a/Modules/Tools/Externals/Zend/Db/Adapter/Pdo/Abstract.php b/Modules/Tools/Externals/Zend/Db/Adapter/Pdo/Abstract.php
index d7f6d8a..ee63c59 100644
--- a/Modules/Tools/Externals/Zend/Db/Adapter/Pdo/Abstract.php
+++ b/Modules/Tools/Externals/Zend/Db/Adapter/Pdo/Abstract.php
@@ -57,7 +57,7 @@ abstract class Zend_Db_Adapter_Pdo_Abstract extends Zend_Db_Adapter_Abstract
*
* @return string
*/
- protected function _dsn()
+ protected function _dsn($write_mode = false)
{
// baseline of DSN parts
$dsn = $this->_config;
@@ -65,10 +65,15 @@ abstract class Zend_Db_Adapter_Pdo_Abstract extends Zend_Db_Adapter_Abstract
// don\'t pass the username,password,charset,persistent and driver_options in the DSN
unset($dsn[\'username\']);
unset($dsn[\'password\']);
+ unset($dsn[\'username_write\']);
+ unset($dsn[\'password_write\']);
unset($dsn[\'options\']);
unset($dsn[\'charset\']);
unset($dsn[\'persistent\']);
unset($dsn[\'driver_options\']);
+
+ if ($write_mode) $dsn[\'host\'] = $dsn[\'host_write\'];
+ unset($dsn[\'host_write\']);
// use all remaining parts in the DSN
foreach ($dsn as $key => $val) {
@@ -91,9 +96,6 @@ abstract class Zend_Db_Adapter_Pdo_Abstract extends Zend_Db_Adapter_Abstract
return;
}
// get the dsn first,because some adapters alter the $_pdoType
$dsn = $this->_dsn();
+ if ($this->_config[\'host_write\'])
+ $dsn_write = $this->_dsn(true);
// check for PDO extension
if (!extension_loaded(\'pdo\')) {
/**
@@ -120,14 +122,28 @@ abstract class Zend_Db_Adapter_Pdo_Abstract extends Zend_Db_Adapter_Abstract
$this->_config[\'driver_options\'][PDO::ATTR_PERSISTENT] = true;
}
try {
$this->_connection = new PDO(
- $dsn,+ $dsn_read,$this->_config[\'username\'],$this->_config[\'password\'],$this->_config[\'driver_options\']
);
+ if ($this->_config[\'host_write\']) {
+ $this->_connection_write = new PDO(
+ $dsn_write,+ $this->_config[\'username_write\'],+ $this->_config[\'password_write\'],+ $this->_config[\'driver_options\']
+ );
+ }
+
$this->_profiler->queryEnd($q);
// set the PDO connection to perform case-folding on array keys,or not
diff --git a/Modules/Tools/Externals/Zend/Db/Statement/Pdo.php b/Modules/Tools/Externals/Zend/Db/Statement/Pdo.php
index 8bd9f98..4ab81bf 100644
--- a/Modules/Tools/Externals/Zend/Db/Statement/Pdo.php
+++ b/Modules/Tools/Externals/Zend/Db/Statement/Pdo.php
@@ -61,8 +61,11 @@ class Zend_Db_Statement_Pdo extends Zend_Db_Statement implements IteratorAggrega
*/
protected function _prepare($sql)
{
+
+ $read_only_connection = preg_match(\"/^select/i\",$sql);
+
try {
- $this->_stmt = $this->_adapter->getConnection()->prepare($sql);
+ $this->_stmt = $this->_adapter->getConnection($read_only_connection)->prepare($sql);
} catch (PDOException $e) {
require_once \'Zend/Db/Statement/Exception.php\';
throw new Zend_Db_Statement_Exception($e->getMessage());