前言

大家可以从任何一个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
# 分库分表
# 分库
# 分表
# php 分库分表hash算法
# 1亿条数据如何分表100张到Mysql数据库中(PHP)
# PHP操作mysql数据库分表的方法
# php实现mysql数据库分表分段备份
# Yii2.0高级框架数据库增删改查的一些操作
# YII2数据库查询实践
# Yii2框架数据库简单的增删改查语法小结
# yii2.0数据库迁移教程【多个数据库同时同步数据】
# Yii2——使用数据库操作汇总(增删查改、事务)
# 使用Yii2实现主从数据库设置
# 多个
# 都是
# 库里
# 接了
# 就可以
# 是一个
# 几个
# 就会
# 是因为
# 走了
# 都要
# 这就是
# 基础上
# 只需
# 是从
# 提出了
# 理所应当
# 要把
# 可以通过
# 不存在
相关文章:
建站之星如何实现PC+手机+微信网站五合一建站?
网站制作公司广州有几家,广州尚艺美发学校网站是多少?
文字头像制作网站推荐软件,醒图能自动配文字吗?
建站之星24小时客服电话如何获取?
网站制作怎么样才能赚钱,用自己的电脑做服务器架设网站有什么利弊,能赚钱吗?
GML (Geography Markup Language)是什么,它如何用XML来表示地理空间信息?
电脑免费海报制作网站推荐,招聘海报哪个网站多?
购物网站制作费用多少,开办网上购物网站,需要办理哪些手续?
浅析上传头像示例及其注意事项
如何在云主机上快速搭建多站点网站?
建站之星图片链接生成指南:自助建站与智能设计教程
怎么制作一个起泡网,水泡粪全漏粪育肥舍冬季氨气超过25ppm,可以有哪些措施降低舍内氨气水平?
学校免费自助建站系统:智能生成+拖拽设计+多端适配
青岛网站设计制作公司,查询青岛招聘信息的网站有哪些?
建站之星IIS配置教程:代码生成技巧与站点搭建指南
如何用wdcp快速搭建高效网站?
如何选择高性价比服务器搭建个人网站?
重庆网站制作公司哪家好,重庆中考招生办官方网站?
建站之星logo尺寸如何设置最合适?
电商网站制作公司有哪些,1688网是什么意思?
建站之星如何一键生成手机站?
如何用5美元大硬盘VPS安全高效搭建个人网站?
免费制作海报的网站,哪位做平面的朋友告诉我用什么软件做海报比较好?ps还是cd还是ai这几个软件我都会些我是做网页的?
建站主机服务器选型指南与性能优化方案解析
制作企业网站建设方案,怎样建设一个公司网站?
建站之星安装后如何自定义网站颜色与字体?
h5在线制作网站电脑版下载,h5网页制作软件?
IOS倒计时设置UIButton标题title的抖动问题
如何高效生成建站之星成品网站源码?
c++怎么使用类型萃取type_traits_c++ 模板元编程类型判断【方法】
如何在阿里云ECS服务器部署织梦CMS网站?
制作网站的软件免费下载,免费制作app哪个平台好?
北京建设网站制作公司,北京古代建筑博物馆预约官网?
新网站制作渠道有哪些,跪求一个无线渠道比较强的小说网站,我要发表小说?
深圳防火门网站制作公司,深圳中天明防火门怎么编码?
建站之家VIP精选网站模板与SEO优化教程整合指南
如何打造高效商业网站?建站目的决定转化率
建站DNS解析失败?如何正确配置域名服务器?
最好的网站制作公司,网购哪个网站口碑最好,推荐几个?谢谢?
详解免费开源的.NET多类型文件解压缩组件SharpZipLib(.NET组件介绍之七)
网站制作专业公司有哪些,如何制作一个企业网站,建设网站的基本步骤有哪些?
,制作一个手机app网站要多少钱?
c# Task.ConfigureAwait(true) 在什么场景下是必须的
建站之星CMS五站合一模板配置与SEO优化指南
企业微网站怎么做,公司网站和公众号有什么区别?
在线教育网站制作平台,山西立德教育官网?
建站之星后台管理如何实现高效配置?
番禺网站制作公司哪家值得合作,番禺图书馆新馆开放了吗?
建站之星后台密码遗忘?如何快速找回?
如何在橙子建站中快速调整背景颜色?
*请认真填写需求信息,我们会在24小时内与您取得联系。