Read/Write splits using Zend_Db

杀马特。学长 韩版系。学妹 提交于 2019-12-04 17:09:22

As you said MySQlProxy can be a solution, but I personnaly never tested it out in production.

I use 2 Db connections in my code to split-out write and read requests. 80% of the usual tasks are done with the read connection. You could use the Zend_Application_Resource_Multidb to handle that (For me I've done this part long before and I simply store a second Db connection in the registry).

  • First limit your user rights only on read operation and create another db user with write authorization.
  • then track every write request in your code ("update", "insert", "delete" is a good start) and try to make all these calls with a dedicated helper.
  • run your app and watch it crash, then fix problems :-)

It's easier when you think this problem in the beginning. For example:

  • I usually have a Zend_Db_Table factory, taking a 'read' or 'write' parameter, and giving me a Singleton of the right Zend_Db_Table (a dual singleton, it I can have a read instance and a write instance). Then I only need to ensure I use the right initialized Zend_Db_Table when I use write access queries/operations. Notice that memory usage is far better when using Zend_Db_Table as singletons.
  • I try to get all write operations in a TransactionHandler. I there I can check I use only objects linked with the right connection. Transactions are then managed on controllers, I never try to manage transaction in Database layers, all start/commit/rollback thinking is done on the controllers (or another conceptual layer, but not the DAO layer).

This last point, transactions, is important. If you want to manage transaction it's important to make the READ requests INSIDE the transaction, with the WRITE-enabled connection. As all reads done before the transaction should be considered as outdated, and if your database backend is doing implicits locks you'll have to make the read request to get the locks. If your database backend is not doing implicit reads then you'll have to perform the row locks in the transaction as well. And that mean you should'nt rely on the SELECT keyword to push that request on the read-only connection.

If you have a nice db layer usage in your application the change is not really hard to make. If you made chaotic things with your database/DAO layer then... it may be harder.

h2. Zend

I just have patched Zend PDO_MYSQL to separate read-write connections. For this you will need just specify additional parameters in applicaiton configs:

'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'
        )
    ),

Here all "SELECT ..." queries will use host. And all other queries will use *host_write*. If host_write not specified, then all queries use host.

Patch:

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());
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!