连接总体流程

随机打乱从库配置(master可以选择是否打乱,slave一定会打乱;如果master没有配置数组则直接使用$dsn和$username作为master的配置,也就是一主)

遍历配置如果该库的配置在serverStatusCache缓存中生效则说明过期时间内该配置不可用,直接continue

如果缓存无值则去实例化PDO(会新实例化一个类$db = Yii::createObject($config))

实例化PDO记录info日志

记录实例化性能分析日志(可选,根据$enableProfiling)

new PDO

设置PDO的ATTR_ERRMODE、ATTR_EMULATE_PREPARES、字符集属性

执行afterOpen事件

实例化失败记录serverStatusCache缓存,标识该配置600秒(默认)内不可用

master与slave连接的差异

slave连接会先判断是否可以使用slave从库($this->enableSlaves)

slave如果连接不上会判断是否进而连接master

一定会打乱slave配置数组

如果没有master配置数组,则直接使用$this->dns和$this->root

master连接可以选择是否打乱配置数组

涉及方法

getSlavePdo($fallbackToMaster = true),会调用getSlave(false),如果从库连不上就去连接master(根据参数$fallbackToMaster),返回的是PDO类

getSlave($fallbackToMaster = true),会调用openFromPool,从slave配置数组中随机连接一个slave,返回的是Connection类

getMasterPdo(),会调用open(),如果master配置数据为空则直接使用$dns进行连接,如果master配置数组不为空则遍历连接master,返回PDO类

getMaster(),遍历连接master,返回Connection类

openFromPool(array $pool, array $sharedConfig),随机打乱配置信息

openFromPoolSequentially(array $pool, array $sharedConfig),不随机打乱配置信息,遍历配置信息,连接PDO;内部还有serverStatusCache去缓存服务器可用状态

open(),连接master或者slave,会记录info和Profiling日志(可选)

createPdoInstance(),实例化PDO

属性注入

因为Connection继承Component类,可以使用属性注入,所以

$db->master; //和$db->getMaster();一样

$db->slave; //和$db->getSlave();一样

$db->masterPdo; //和$db->getMasterPdo();一样

$db->slavePdo; //和$db->getSlavePdo();一样

源码细节

yii2中可以配置一主多从配置,在连接从库方面数据库配置如下

public function actionD(){

$db = new \yii\db\Connection([

'dsn' => 'mysql:host=192.168.124.10;dbname=test',

'username' => 'root',

'password' => '',

'charset' => 'utf8',

'enableSlaves'=>true, //可以使用从库

'serverRetryInterval'=>600, //其中一个从库配置不可用,将缓存不可用状态600秒

'enableProfiling'=>true, //默认配置,将记录连接数据库、执行语句等的性能分析日志

'emulatePrepare'=>true, //true为开启本地模拟prepare

'slaveConfig'=>[ //从库slaves属性通用配置

'username' => 'root',

'password' => '',

'attributes' => [

PDO::ATTR_TIMEOUT => 10,

],

],

'slaves'=>[ //从库列表

["dsn"=>"mysql:host=192.168.124.11;dbname=test"],

["dsn"=>"mysql:host=192.168.124.12;dbname=test"],

[

"dsn"=>"mysql:host=192.168.124.13;dbname=test",

'username' => 'main',

'password' => '123456',

],

],

'masters'=>[ //主库列表

["dsn"=>"mysql:host=192.168.124.11;dbname=test"],

["dsn"=>"mysql:host=192.168.124.12;dbname=test"],

[

"dsn"=>"mysql:host=192.168.124.13;dbname=test",

'username' => 'main',

'password' => '123456',

],

],

'masterConfig'=>[ //主库master属性通用配置

'username' => 'root',

'password' => '',

'attributes' => [

PDO::ATTR_TIMEOUT => 10,

],

],

]);

$slave = $db->getSlavePdo();

$slave = $db->getSlave();

return 123;

}

}

可以看到数据库操作的类是\yii\db\Connection,该类继承Component类,可见可以使用属性注入、行为和事件

针对Connection的属性注入,只有以下属性是私有的,以下属性一般不会在外部进行操作

private $_transaction;

private $_schema;

private $_driverName;

private $_master = false;

private $_slave = false;

private $_queryCacheInfo = [];

针对Connection的事件,可以注册以下事件

const EVENT_AFTER_OPEN = 'afterOpen'; //连接数据库后的事件

const EVENT_BEGIN_TRANSACTION = 'beginTransaction'; //开启事务的事件

const EVENT_COMMIT_TRANSACTION = 'commitTransaction'; //提交事务的事件

const EVENT_ROLLBACK_TRANSACTION = 'rollbackTransaction'; //回滚的事件

Connection类使用的mysql操作对象是PDO,涉及方法有

public function getSlavePdo($fallbackToMaster = true)

public function getSlave($fallbackToMaster = true)

追进在getSlavePdo方法,可见当slave连接不可用时候,会默认连接主库($fallbackToMaster=true)

public function getSlavePdo($fallbackToMaster = true){

$db = $this->getSlave(false); //进行slave连接

if ($db === null) {

return $fallbackToMaster ? $this->getMasterPdo() : null; //当slave不可用时候,是否连接主库

}

return $db->pdo; //返回数据库连接资源,从库和主库都连接不上的话会返回null

}

追进getSlave方法

public function getSlave($fallbackToMaster = true){

if (!$this->enableSlaves) {//判断是否可以使用slave

return $fallbackToMaster ? $this : null;

}

if ($this->_slave === false) { //如果还没有连接过slave库,就进行连接

$this->_slave = $this->openFromPool($this->slaves, $this->slaveConfig); //将slave配置信息给openFromPool方法

}

return $this->_slave === null && $fallbackToMaster ? $this : $this->_slave;

}

追进openFromPool方法,可见该方法就是将$this->slaves从库dsn配置打乱,让第一次连接slave随机化

protected function openFromPool(array $pool, array $sharedConfig){

shuffle($pool); //打乱从库配置

return $this->openFromPoolSequentially($pool, $sharedConfig);

}

openFromPoolSequentially方法

protected function openFromPoolSequentially(array $pool, array $sharedConfig){

if (empty($pool)) { //是否有slave配置池,如果没有的话就是最后返回给$this->_slave为null

return null;

}

if (!isset($sharedConfig['class'])) { //判断$this->slaveConfig属性是否有class,可以设置class将从库的连接配置成自己重新的类

$sharedConfig['class'] = get_class($this);

}

//服务状态缓存,使用依赖注入获取cache缓存类

$cache = is_string($this->serverStatusCache) ? Yii::$app->get($this->serverStatusCache, false) : $this->serverStatusCache;

//遍历slave配置池

foreach ($pool as $config) {

//合并配置

$config = array_merge($sharedConfig, $config);

if (empty($config['dsn'])) {

throw new InvalidConfigException('The "dsn" option must be specified.');

}

$key = [__METHOD__, $config['dsn']];

//这里就是判断缓存是否有值,如果有的话说明在过期时间内该配置的slave不可用

if ($cache instanceof CacheInterface && $cache->get($key)) {

// should not try this dead server now

continue;

}

//通过依赖注入创建了一个类,该类专门是这个slave的

$db = Yii::createObject($config);

try {

$db->open();

return $db;

} catch (\Exception $e) {

//记录日志

Yii::warning("Connection ({$config['dsn']}) failed: " . $e->getMessage(), __METHOD__);

if ($cache instanceof CacheInterface) {

//将该配置的slave服务不可用状态存缓存,值是1,过期时间的$this->serverRetryInterval秒

$cache->set($key, 1, $this->serverRetryInterval);

}

}

}

return null;

}

在TestController控制器的配置中,可见会随机打乱slaves属性,如果有任何一个从库连接上了就是直接返回,如果有连接不上的就会将不可用状态存缓存,然后继续循环

slaveConfig属性是一个从库的通用配置,会循环的去array_merge()属性slaves

所以配置

'slaveConfig'=>[ //从库slaves属性通用配置

'username' => 'root',

'password' => '',

'attributes' => [

PDO::ATTR_TIMEOUT => 10,

],

],

'slaves'=>[ //从库列表

["dsn"=>"mysql:host=192.168.124.11;dbname=test"],

["dsn"=>"mysql:host=192.168.124.12;dbname=test"],

[

"dsn"=>"mysql:host=192.168.124.13;dbname=test",

'username' => 'main',

'password' => '123456',

'class'=> yii\overload\myDB

],

]

最后生成的配置为(这个配置会被shuffle函数打乱顺序)

'slaves'=>[ //从库列表

[

"dsn"=>"mysql:host=192.168.124.11;dbname=test",

'username' => 'root',

'password' => '',

'attributes' => [

PDO::ATTR_TIMEOUT => 10,

],

'class' => 'yii\db\Connection',

],

[

"dsn"=>"mysql:host=192.168.124.12;dbname=test"

'username' => 'root',

'password' => '',

'attributes' => [

PDO::ATTR_TIMEOUT => 10,

],

'class' => 'yii\db\Connection',

],

[

"dsn"=>"mysql:host=192.168.124.13;dbname=test",

'username' => 'main',

'password' => '123456',

'class'=> yii\overload\myDB

],

]

在open方法中,因为是重新new,所以$this->pdo和$this->master都是null

public function open(){

//因为是重新new,所以$this->pdo和$this->master都是null

if ($this->pdo !== null) {

return;

}

//因为是重新new,所以$this->pdo和$this->master都是null

if (!empty($this->masters)) {

$db = $this->getMaster();

if ($db !== null) {

$this->pdo = $db->pdo;

return;

}

throw new InvalidConfigException('None of the master DB servers is available.');

}

if (empty($this->dsn)) {

throw new InvalidConfigException('Connection::dsn cannot be empty.');

}

$token = 'Opening DB connection: ' . $this->dsn;

$enableProfiling = $this->enableProfiling;

try {

//记录日志

Yii::info($token, __METHOD__);

//如果开启了性能分析,则记录性能分析日志(性能分析开启)

if ($enableProfiling) {

Yii::beginProfile($token, __METHOD__);

}

$this->pdo = $this->createPdoInstance();

$this->initConnection();

//如果开启了性能分析,则记录性能分析日志(性能分析关闭)

if ($enableProfiling) {

Yii::endProfile($token, __METHOD__);

}

} catch (\PDOException $e) {

if ($enableProfiling) {

Yii::endProfile($token, __METHOD__);

}

throw new Exception($e->getMessage(), $e->errorInfo, (int) $e->getCode(), $e);

}

}

在createPdoInstance方法中,这个没什么好说的,就是执行new PDO

protected function createPdoInstance(){

$pdoClass = $this->pdoClass;

if ($pdoClass === null) {

$pdoClass = 'PDO';

if ($this->_driverName !== null) {

$driver = $this->_driverName;

} elseif (($pos = strpos($this->dsn, ':')) !== false) {

$driver = strtolower(substr($this->dsn, 0, $pos));

}

if (isset($driver)) {

if ($driver === 'mssql' || $driver === 'dblib') {

$pdoClass = 'yii\db\mssql\PDO';

} elseif ($driver === 'sqlsrv') {

$pdoClass = 'yii\db\mssql\SqlsrvPDO';

}

}

}

$dsn = $this->dsn;

if (strncmp('sqlite:@', $dsn, 8) === 0) {

$dsn = 'sqlite:' . Yii::getAlias(substr($dsn, 7));

}

return new $pdoClass($dsn, $this->username, $this->password, $this->attributes);

}

在initConnection方法中,这个也没什么好说的,就是去设置PDO属性和执行afterOpen事件

protected function initConnection(){

$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) {

$this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare);

}

if ($this->charset !== null && in_array($this->getDriverName(), ['pgsql', 'mysql', 'mysqli', 'cubrid'], true)) {

$this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset));

}

$this->trigger(self::EVENT_AFTER_OPEN);

}

主库连接getMaterPdo()方法

public function getMasterPdo(){

$this->open();

return $this->pdo;

}

主库连接getMaster()方法

public function getMaster(){

if ($this->_master === false) {

$this->_master = $this->shuffleMasters //是否随机打乱master配置数组

? $this->openFromPool($this->masters, $this->masterConfig)

: $this->openFromPoolSequentially($this->masters, $this->masterConfig);

}

return $this->_master;

}

yii连接mysql主从_Connection 数据库主从连接源码剖析相关推荐

  1. r 连接 mysql sqldf_R语言︱ 数据库SQL-R连接与SQL语句执行(RODBC、sqldf包)

    数据库是极其重要的R语言数据导入源数据之地,读入包有sqldf.RODBC等.跟SQL server相连有RODBC,跟MySQL链接的有RMySQL.但是在R里面,回传文本会出现截断的情况,这一情况 ...

  2. java连接mysql 5.7数据库_javaJDBC连接mysql(5.7)数据库,一看就懂的详细例子

    不多比比 直接上代码 package Demo; import java.sql.Connection; import java.sql.DriverManager; import java.sql. ...

  3. php mysql odbc_javascript连接mysql与php通过odbc连接任意数据库的实例

    脑洞大开用javascript链接mysql,2个小时总算实现了,用到了odbc,后面又想到用php链接odbc链接数据库,也实现了,就把案例放一下. 注意事项: 1.javascript连接mysq ...

  4. Node.js 连接 MySQL 并进行数据库操作 –node.js 开发指南

    Node.js是一套用来编写高性能网络服务器的JavaScript工具包 通常在NodeJS开发中我们经常涉及到操作数据库,尤其是 MySQL ,作为应用最为广泛的开源数据库则成为我们的首选,本篇就来 ...

  5. Python连接MySQL、PostgreSQL数据库(简单便捷)

    一.安装库 Python连接MySQL.PostgreSQL数据库需要导入相关的模块,分别是"pymysql"和"psycopg2"模块,我们可以在Pychar ...

  6. jsp mysql oracle_Jsp 连接 mySQL、Oracle 数据库备忘

    Jsp 连接 mySQL.Oracle 数据库备忘 2009-12-15 16:47 Jsp 环境目前最流行的是 Tomcat5.0.Tomcat5.0 自己包含一个 Web 服务器,如果是测试,就没 ...

  7. Node.js+MySQL开发的B2C商城系统源码+数据库(微信小程序端+服务端),界面高仿网易严选商城

    下载地址:Node.js+MySQL开发的B2C商城系统源码+数据库(微信小程序端+服务端) NideShop商城(微信小程序端) 界面高仿网易严选商城(主要是2016年wap版) 测试数据采集自网易 ...

  8. (精品)ssm Java mysql maven vue健康医疗预约系统(源码+系统+mysql数据库+lw文档)

    下载地址:https://download.csdn.net/download/m0_71595576/85519044 项目介绍: (精品)ssm Java mysql maven vue健康医疗预 ...

  9. 基于SSM框架+MySQL的超市订单管理系统【源码+文档+PPT】

    目录 1.系统需求分析 1.1 系统功能分析 1.2 系统功能需求 1.3 系统性能需求 2.数据库设计 2.1 数据库需求分析 3.数据库物理结构设计 4.各功能模块的设计与实现 4.1 开发框架 ...

最新文章

  1. 0227windows下模糊查询oracle事件的脚本
  2. Python常用内置函数(二)
  3. 解决cocoapods在64位iOS7系统以下的警告问题
  4. 2022年十大科技应用趋势 | 万字报告PDF
  5. solr后台界面介绍——(十一)
  6. matlab的一个疑问?
  7. VB6调用API打开目标文件所在文件夹且选中目标文件
  8. JAVA入门级教学之(封装)
  9. oracle11g 导出表报EXP-00011:table不存在。
  10. IE9相比IE8丢失了什么?
  11. 回到顶部 jquery
  12. 佳能 2900 linux 驱动下载,在Linux下安装打印驱动,以佳能LBP2900+为例
  13. 艾永亮:优衣库,究竟是怎么卖衣服的?
  14. 计算机开机最快设置,w7提高开机速度如何操作_win7电脑怎么开机更快
  15. ps裁剪和裁切的区别_PS图片的裁剪和裁切的含义和应用
  16. 两张图片怎样合成一张左右拼图?
  17. 元宇宙011 | 元宇宙的沉浸式体验会成瘾吗?
  18. 什么是Library
  19. 终于搞懂Dijkstra算法了
  20. java走迷宫_走迷宫问题Java递归

热门文章

  1. 循环神经网络应用案例
  2. php 可以动态的new一个变量类名
  3. yum安装mysql和mysql源,配置mysql(亲测)
  4. 常见的网站服务器架构有哪些?
  5. PHP内存管理 垃圾回收
  6. Golang类型转化方法汇总
  7. Linux安装composer出现usr/bin/env: php: No such file or directory)
  8. jQuery的var that=this
  9. android 上线apk,码云 Android apk 在线构建功能上线啦!
  10. 基于h5的跳一跳游戏的开发与实现_「南宁小程序开发」企业开发小程序有哪些好处?...