前言

大家可以从任何一个gii生成model类开始代码上溯,会发现:yii2的model层基于ActiveRecord实现DAO访问数据库的能力。

而ActiveRecord的继承链可以继续上溯,最终会发现model其实是一个component,而component是yii2做IOC的重要组成部分,提供了behaviors,event的能力供继承者扩展。

(IOC,component,behaviors,event等概念可以参考http://www.digpage.com/学习)

先不考虑上面的一堆概念,一个站点发展历程一般是1个库1个表,1个库N个表,M个库N个表这样走过来的,下面拿订单表为例,分别说说。

1)1库1表:yii2默认采用PDO连接mysql,框架默认会配置一个叫做db的component作为唯一的mysql连接对象,其中dsn分配了数据库地址,数据库名称,配置如下:

'components' => [
 'db' => [
 'class' => 'yii\db\Connection',
 'dsn' => 'mysql:host=10.10.10.10;port=4005;dbname=wordpress',
 'username' => 'wp',
 'password' => '123',
 'charset' => 'utf8',
 ],

这就是yii2做IOC的一个典型事例,model层默认就会取这个db做为mysql连接对象,所以model访问都经过这个connection,可以从ActiveRecord类里看到。

class ActiveRecord extends BaseActiveRecord {
 
/**
 * Returns the database connection used by this AR class.
 * By default, the "db" application component is used as the database connection.
 * You may override this method if you want to use a different database connection.
 * @return Connection the database connection used by this AR class.
 */
public static function getDb()
{
 return Yii::$app->getDb();
}

追踪下去,最后会走yii2的ioc去创建名字叫做”db”的这个component返回给model层使用。

abstract class Application extends Module {
/**
 * Returns the database connection component.
 * @return \yii\db\Connection the database connection.
 */
public function getDb()
{
 return $this->get('db');
}

yii2上述实现决定了只能连接了1台数据库服务器,选择了其中1个database,那么具体访问哪个表,是通过在Model里覆写tableName这个static方法实现的,ActiveRecord会基于覆写的tableName来决定表名是什么。

class OrderInfo extends \yii\db\ActiveRecord
{
 /**
 * @inheritdoc
 * @return
 */
 public static function tableName()
 {
 return 'order_info';
 }

 2)1库N表:因为orderInfo数据量变大,各方面性能指标有所下降,而单机硬件性能还有较大冗余,于是可以考虑分多张order_info表,均摊数据量。假设我们要份8张表,那么可以依据uid(用户ID)%8来决定订单存储在哪个表里。

然而1库1表的时候,tableName()返回是的order_info,于是理所应当的重载这个函数,提供一种动态变化的能力即可,例如:

class OrderInfo extends \yii\db\ActiveRecord
{
 private static $partitionIndex_ = null; // 分表ID
 
 /**
 * 重置分区id
 * @param unknown $uid
 */
 private static function resetPartitionIndex($uid = null) {
 $partitionCount = \Yii::$app->params['Order']['partitionCount'];
 
 self::$partitionIndex_ = $uid % $partitionCount;
 }
 
 /**
 * @inheritdoc
 */
 public static function tableName()
 {
 return 'order_info' . self::$partitionIndex_;
 }

提供一个resetParitionIndex($uid)函数,在每次操作model之前主动调用来标记分表的下标,并且重载tableName来为model层拼接生成本次操作的表名。

3)M库N表:1库N表逐渐发展,单机存储和性能达到瓶颈,只能将数据分散到多个服务器存储,于是提出了分库的需求。但是从”1库1表”的框架实现逻辑来看,model层默认取db配置作为mysql连接的话,是没有办法访问多个mysql实例的,所以必须解决这个问题。

一般产生这个需求,产品已经进入中期稳步发展阶段。有2个思路解决M库问题,1种是yii2通过改造直连多个地址进行访问多库,1种是yii2仍旧只连1个地址,而这个地址部署了dbproxy,由dbproxy根据你访问的库名代理连接多个库。

如果此前没有熟练的运维过dbproxy,并且php集群规模没有大到单个mysql实例客户端连接数过多拒绝服务的境地,那么第1种方案就可以解决了。否则,应该选择第2种方案。

无论选择哪种方案,我们都应该进一步改造tableName()函数,为database名称提供动态变化的能力,和table动态变化类似。

class OrderInfo extends \yii\db\ActiveRecord {
 
private static $databaseIndex_ = null; // 分库ID
private static $partitionIndex_ = null; // 分表ID
 
 /**
 * 重置分区id
 * @param unknown $uid
 */
 private static function resetPartitionIndex($uid = null) {
 $databaseCount = \Yii::$app->params['Order']['databaseCount'];
 $partitionCount = \Yii::$app->params['Order']['partitionCount'];
 
 // 先决定分到哪一张表里
 self::$partitionIndex_ = $uid % $partitionCount;
 // 再根据表的下标决定分到哪个库里
 self::$databaseIndex_ = intval(self::$partitionIndex_ / ($partitionCount / $databaseCount));
 }
 
 /**
 * @inheritdoc
 */
 public static function tableName()
 {
 $database = 'wordpress' . self::$databaseIndex_;
 $table = 'order_info' . self::$partitionIndex_;
 return $database . '.' . $table;
 }

在分表逻辑基础上稍作改造,即可实现分库。假设分8张表,那么分别是00,01,02,03…07,然后决定分4个库,那么00,01表在00库,02,03表在01库,04,05表在02库,06,07表在03库,根据这个规律对应的计算代码如上。最终ActiveRecord生效的代码都会类似于”select * from wordpress0.order_info1″,这样就可以解决连接dbproxy访问多库的需求了。

那么yii直接访问多Mysql实例怎么做呢,其实类似tableName() ,我们只需要覆盖getDb()方法即可,同时要求我们首先配置好4个mysql实例,从而可以通过yii的application通过IOC设计来生成多个db连接,所有改动如下:

先配置好4个数据库,给予不同的component id以便区分,它们连接了不同的mysql实例,其中dsn里的dbname只要存在即可(防止PDO执行use database时候不存在报错),真实的库名是通过tableName()动态变化的。

'db0' => [
 'class' => 'yii\db\Connection',
 'dsn' => 'mysql:host=10.10.10.10;port=6184;dbname=wordpress0',
 'username' => 'wp',
 'password' => '123',
 'charset' => 'utf8',
 // 'tablePrefix' => 'ktv_',
],
'db1' => [
 'class' => 'yii\db\Connection',
 'dsn' => 'mysql:host=10.10.10.11;port=6184;dbname=wordpress2',
 'username' => 'wp',
 'password' => '123',
 'charset' => 'utf8',
 // 'tablePrefix' => 'ktv_',
],
'db2' => [
 'class' => 'yii\db\Connection',
 'dsn' => 'mysql:host=10.10.10.12;port=6184;dbname=wordpress4',
 'username' => 'wp',
 'password' => '123',
 'charset' => 'utf8',
 // 'tablePrefix' => 'ktv_',
],
'db3' => [
 'class' => 'yii\db\Connection',
 'dsn' => 'mysql:host=10.10.10.13;port=6184;dbname=wordpress6',
 'username' => 'wp',
 'password' => '123',
 'charset' => 'utf8',
 // 'tablePrefix' => 'ktv_',
],

覆写getDb()方法,根据库下标返回不同的数据库连接即可。

class OrderInfo extends \yii\db\ActiveRecord
{
 private static $databaseIndex_ = null; // 分库ID
 private static $partitionIndex_ = null; // 分表ID
 
 /**
 * 重置分区id
 * @param unknown $uid
 */
 private static function resetPartitionIndex($uid = null) {
 $databaseCount = \Yii::$app->params['Order']['databaseCount'];
 $partitionCount = \Yii::$app->params['Order']['partitionCount'];
 
 // 先决定分到哪一张表里
 
 self::$partitionIndex_ = $uid % $partitionCount;
 // 再根据表的下标决定分到哪个库里
 self::$databaseIndex_ = intval(self::$partitionIndex_ / ($partitionCount / $databaseCount));
 }
 
 /**
 * 根据分库分表,返回库名.表名
 */
 public static function tableName()
 {
 $database = 'wordpress' . self::$databaseIndex_;
 $table = 'order_info' . self::$partitionIndex_;
 return $database . '.' . $table;
 }
 
 /**
 * 根据分库结果,返回不同的数据库连接
 */
 public static function getDb()
 {
 return \Yii::$app->get('db' . self::$databaseIndex_);
 }

这样,无论是yii连接多个mysql实例,还是yii连接1个dbproxy,都可以实现了。

网上有一些例子,试图通过component的event机制,通过在component的配置中指定onUpdate,onBeforeSave等自定义event去hook不同的DAO操作来隐式(自动)的变更database或者connection或者tablename的做法,都是基于model object才能实现的,如果直接使用model class的类似updateAll()方法的话,是绕过DAO直接走了PDO的,不会触发这些event,所以并不是完备的解决方案。

这样的方案原理简单,方案对框架无侵入,只是每次DB操作前都要显式的resetPartitionIndex($uid)调用。如果要做到用户无感知,那必须对ActiveRecord类进行继承,进一步覆盖所有class method的实现以便插入选库选表逻辑,代价过高。

补充:关于分库分表的一些实践细节,分表数量建议2^n,例如n=3的情况下分8张表,然后确定一下几个库,库数量是2^m,但要<=表数量,例如这里1个库,2个库,4个库,8个库都是可以的,表顺序坐落在这些库里即可。
为什么数量都是2指数,是因为如果面临扩容需求,数据的迁移将方便一些。假设分了2张表,数据按uid%2打散,要扩容成4张表,那么只需要把表0的部分数据迁移到表2,表1的部分数据迁移到表3,即可完成扩容,也就是uid%2和uid%4造成的迁移量是很小的,这个可以自己算一下。

总结

以上就是关于yii2实现分库分表的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

详解yii2实现分库分表的方案与思路的更多相关文章

  1. Yii2中组件的注册与创建方法

    这篇文章主要介绍了Yii2之组件的注册与创建的实现方法,非常不错,具有参考借鉴价值,需要的朋友可以参考下

  2. 深入浅析yii2-gii自定义模板的方法

    Yii 是一个高性能,基于组件的 PHP 框架,用于快速开发现代 Web 应用程序。接下来通过本文给大家介绍yii2-gii自定义模板的方法,需要的朋友参考下吧

  3. Yii2语言国际化自动配置详解

    这篇文章主要介绍了Yii2语言国际化自动配置详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  4. php Yii2框架创建定时任务方法详解

    Yii2是一个基于组件、用于开发大型Web应用的高性能PHP框架,采用严格的OOP编写,并有着完善的库引用以及全面的教程,该框架提供了Web 2.0应用开发所需要的几乎一切功能,是最有效率的PHP框架之一

  5. YII2框架中ActiveDataProvider与GridView的配合使用操作示例

    这篇文章主要介绍了YII2框架中ActiveDataProvider与GridView的配合使用操作,结合实例形式分析了YII2框架中ActiveDataProvider与GridView的功能及配合使用相关操作实现技巧,需要的朋友可以参考下

  6. Laravel模型间关系设置分表的方法示例

    这篇文章主要给大家介绍了关于Laravel模型间关系设置分表的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。

  7. Yii2中添加全局函数的方法分析

    这篇文章主要介绍了Yii2中添加全局函数的方法,结合实例形式对比分析了2种添加全局函数的实现技巧,需要的朋友可以参考下

  8. yii2带搜索功能的下拉框实例详解

    带搜索功能下拉框在项目中经常会用到,下面小编把实现代码分享到脚本之家平台,供大家参考

  9. 使用YII2框架实现微信公众号中表单提交功能

    刚刚接触微信就要做一个表单提交功能,需求是这样的只能在数据库中存在的手机号看到表单。下面通过本文给大家分享使用YII2框架实现微信公众号中表单提交功能,感兴趣的朋友一起看看吧

  10. Yii2单元测试用法示例

    这篇文章主要介绍了Yii2单元测试用法,结合实例形式分析了Yii2单元测试的具体实现与使用技巧,需要的朋友可以参考下

随机推荐

  1. PHP个人网站架设连环讲(一)

    先下一个OmnihttpdProffesinalV2.06,装上就有PHP4beta3可以用了。PHP4给我们带来一个简单的方法,就是使用SESSION(会话)级变量。但是如果不是PHP4又该怎么办?我们可以假设某人在15分钟以内对你的网页的请求都不属于一个新的人次,这样你可以做个计数的过程存在INC里,在每一个页面引用,访客第一次进入时将访问时间送到cookie里。以后每个页面被访问时都检查cookie上次访问时间值。

  2. PHP函数学习之PHP函数点评

    PHP函数使用说明,应用举例,精简点评,希望对您学习php有所帮助

  3. ecshop2.7.3 在php5.4下的各种错误问题处理

    将方法内的函数,分拆为2个部分。这个和gd库没有一点关系,是ecshop程序的问题。会出现这种问题,不外乎就是当前会员的session或者程序对cookie的处理存在漏洞。进过本地测试,includes\modules\integrates\ecshop.php这个整合自身会员的类中没有重写integrate.php中的check_cookie()方法导致,验证cookie时返回的username为空,丢失了登录状态,在ecshop.php中重写了此方法就可以了。把他加到ecshop.php的最后面去就可

  4. NT IIS下用ODBC连接数据库

    $connection=intodbc_connect建立数据库连接,$query_string="查询记录的条件"如:$query_string="select*fromtable"用$cur=intodbc_exec检索数据库,将记录集放入$cur变量中。再用while{$var1=odbc_result;$var2=odbc_result;...}读取odbc_exec()返回的数据集$cur。最后是odbc_close关闭数据库的连接。odbc_result()函数是取当前记录的指定字段值。

  5. PHP使用JpGraph绘制折线图操作示例【附源码下载】

    这篇文章主要介绍了PHP使用JpGraph绘制折线图操作,结合实例形式分析了php使用JpGraph的相关操作技巧与注意事项,并附带源码供读者下载参考,需要的朋友可以参考下

  6. zen_cart实现支付前生成订单的方法

    这篇文章主要介绍了zen_cart实现支付前生成订单的方法,结合实例形式详细分析了zen_cart支付前生成订单的具体步骤与相关实现技巧,需要的朋友可以参考下

  7. Thinkphp5框架实现获取数据库数据到视图的方法

    这篇文章主要介绍了Thinkphp5框架实现获取数据库数据到视图的方法,涉及thinkPHP5数据库配置、读取、模型操作及视图调用相关操作技巧,需要的朋友可以参考下

  8. PHP+jquery+CSS制作头像登录窗(仿QQ登陆)

    本篇文章介绍了PHP结合jQ和CSS制作头像登录窗(仿QQ登陆),实现了类似QQ的登陆界面,很有参考价值,有需要的朋友可以了解一下。

  9. 基于win2003虚拟机中apache服务器的访问

    下面小编就为大家带来一篇基于win2003虚拟机中apache服务器的访问。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  10. Yii2中组件的注册与创建方法

    这篇文章主要介绍了Yii2之组件的注册与创建的实现方法,非常不错,具有参考借鉴价值,需要的朋友可以参考下

返回
顶部