Yii with mysqlnd_ms
在 Yii framework 1.1.x 版本中,官方没有对数据库读写分离提供支持(在2.0版本中已支持),所以我们需要自己实现它。
Yii 扩展社区中 CJ 提供了一个扩展DbConnectionMan来实现数据库的读写分离,但它只能支持在调用 createCommand($sql) 方法,直接传入预编译的SQL语句时有效。也就是说,在使用 ActiveRecord:
Test::model()->find()
或者:
Yii::app()->db->createCommand('select id from test');
中有效,而对于:
Yii::app()->db->createCommand()
->select()
->from('test')
->query()
则是无效的。
因为第三种方式是由CDbCommand生成的SQL语句,并直接使用了CDbCommand::prepare(),而在DbConnectionMan中实现的分离操作是在CDbConnection中对接收到的SQL语句做判断后分离的连接。
如果我们把数据库分离操作移到CDbCOmmand::prepare()中(Yii 2.0版本中的读写分离就是在Command::prepar()中分离的),就可以解决这个问题,但由于框架中没有提供CDbCommand::$_statement和CDbCommand::$_paramLog的访问权限,所以我们暂时无法重载CDbCommand::prepare()来实现分离操作。为此,我向Yii framework开发组提交了一个pull request,希望他们能接受。详见:#3647
##mysqlnd_ms支持
这里,我们使用php的原生扩展mysqlnd_ms在Yii framework中实现读写分离。
由于Yii使用PDO与MySQL数据库进行交互,而如果要在分离中让mysqlnd_ms能够自动的感知到是否是在进行事务操作,就需要对PDO的 ATTR_AUTOCOMMIT操作。
- 事务开始时关闭自动提交
PDO::ATTR_AUTOCOMMIT = false - 事务提交/回滚后重新开启自动提交
PDO::ATTR_AUTOCOMMIT = true
在Yii中,通常使用Yii::app()->db->beginTransaction()来开启一个事务,而默认情况下PDO::ATTR_AUTOCOMMIT是开启的。所以如果想不去一个一个修改代码,就来重写CDbConnection::beginTransaction()吧。
另外,要在提交/回滚后又自动开启自动提交,那就需要重写CDbTransaction::commit()和CDbTransaction::rollback()方法。
CDbConnection::beginTransaction()方法的源码如下:
public function beginTransaction()
{
Yii::trace('Starting transaction','system.db.CDbConnection');
$this->setActive(true);
$this->_pdo->beginTransaction();
return $this->_transaction=new CDbTransaction($this);
}
其中$this->_pdo 和 $this->_transaction 是 private 属性,我们要重写CDbConnection::beginTransaction()肯定需要访问到这两个属性才行。
$this->_pdo和CDbConnection::getPdoInstance()等效,这个没问题。
$this->_transaction稍微麻烦点,框架只提供了CDbConnection::getCurrentTransaction()让我们得到它,并没有提供一个CDbConnection::setCurrentTransaction()来让我们「调戏」它。所以我在 #3647 中同时也提交了一个CDbConnection::setCurrentTransaction()用于支持覆盖$this->_transaction属性。
那么,我们假设CDbConnection::setCurrentTransaction()已经存在了,现在我们开始重写我们自己的CDbConnection和CDbTransaction类:
我们定义一个类叫:MyDbConnection
<?php
class MyDbConnection extends CDbConnection
{
public function beginTransaction()
{
Yii::trace('Starting transaction','system.db.CDbConnection');
$this->setActive(true);
$this->getPdoInstance()->beginTransaction();
$this->setAutoCommit(false); // 注意这里,关闭了自动提交,这是我们重写beginTransaction的目的。
$this->setCurrentTransaction(new MyDbTransaction($this));
return $this->getCurrentTransaction();
}
}
然后我们需要重写 CDbTransaction 类来使得在提交和回滚时自动开启自动提交。
我们定义一个类叫:MyDbTransaction
<?php
class MyDbTransaction extends CDbTransaction
{
public function commit()
{
parent::commit();
$this->getConnection()->setAutoCommit(true);
}
public function rollback()
{
$this->getConnection()->setAutoCommit(true);
}
}
这样一来,我们就可以在不改变现有业务代码的基础上使用mysqlnd_ms进行数据库读写分离了。
别忘了在mysqlnd_ms的配置文件里面添加上 "trx_stickiness": "master"设置