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"
设置