PHP进阶教程-实现一个简单的MySQL连接池

什么是连接池?顾名思义 , 连接池就是一堆预先创建好的连接 , 跟容器会有点像 。连接池主要是在某种需要网络连接的服务 , 提前把连接建立好存起来 , 然后存放在一个池子里面 , 需要用到的时候取出来用 , 用完之后再还回去 。
MySQL连接过程client 建立连接的认证过程

  • 1、server 监听端口
  • 2、client 向server建立TCP连接
  • 3、server 向client发送挑战码报文(报文详细内容在下文中有分析)
  • 4、client 使用挑战码加密密码 , 将加密后的密码包含在回包中 , 发送给server
  • 5、server 根据client的回包 , 验证密码的有效性 , 给client发送ok包或error包
  • 6、client发送SQL执行
  • 7、关闭MySQL关闭、TCP连接

PHP进阶教程-实现一个简单的MySQL连接池

文章插图
 
为什么使用连接池?
PHP进阶教程-实现一个简单的MySQL连接池

文章插图
 
从图可以看到想要执行一条SQL语句每次都要走 图:3.5-1都过程 , 至少要7步才可以成功 。MySQL会有连接数上限的限制 , 而且每次都执行那么多都步骤 , 频繁把时间浪费在了网络IO上 。
没有连接池的做法类似我们买菜做饭 , 比如我们要做十个菜 , 每做一个菜就跑一趟菜市场 , 挑菜、讨价还价、回家、洗菜、下锅、起锅、洗锅;这样是不是很浪费时间?那我们想要做十个菜 , 提前把这十个菜的材料都买回来 , 都洗好备用 , 然后每次炒都时候直接下锅炒就好了 。连接池就是提前买好菜 , 洗好菜(创建连接、验证账号密码) , 在要炒菜的时候直接下锅(执行SQL)炒 。
PHP进阶教程-实现一个简单的MySQL连接池

文章插图
 
使用连接池之后 , 只有在连接池初始化的时候就进行连接然后存到一个容器里面 。每次要执行SQL语句的时候先来这个池获取连接对象 , 然后再发送SQL语句 , 当SQL语句执行完之后 , 再把这个连接归还给连接池 。
使用连接池每次执行SQL语句只需要执行 图:3.5-1 的第6步就行了 , 复用了MySQL连接 , 在高并发情况下 , 节省了每次连接带来的其他开销 。
连接池有什么?
  • 最小连接数
  • 最大连接数
  • 当前连接数
  • 连接池对象
  • 获取连接池超时时间
  • 健康度检查
实战:Swoole实现连接池MysqlPool.php/** * 1、设置配置信息 * 2、创建连接对象 * 3、获取连接对象 * 4、获取连接对象 , 空闲连接不够创建到最大连接数 * 5、执行sql语句 * 6、归还连接 */use SwooleCoroutineChannel;class MysqlPool{// 最小连接数private $min;// 最大连接数private $max;// 当前连接数private $count = 0;// 获取超时时间private $timeOut = 0.2;// 连接池对象容器private $connections;// 配置信息private $config = [];// 连接池对象private static $instance;public function __construct(array $config){$this->config = $config;$this->min = $this->config['min'] ?? 2;$this->max = $this->config['max'] ?? 4;$this->timeOut = $this->config['time_out'] ?? 0.2;$this->connections = new Channel($this->max);}/*** 获取连接池对象* @param null $config* @return MysqlPool*/public static function getInstance($config = null){if (empty(self::$instance)) {if (empty($config)) {throw new RuntimeException("mysql config empty");}self::$instance = new static($config);}return self::$instance;}/*** 初始化连接池* @throws Exception*/public function init(){for ($i = 0; $i < $this->min; $i++) {$this->count++;$mysql = $this->createDb();$this->connections->push($mysql);}}/*** 创建数据库连接对象* @return PDO* @throws Exception*/private function createDb(){$dsn = "mysql:dbname={$this->config['database']};host={$this->config['db_host']}";try {$mysql = new PDO($dsn, $this->config['db_user'], $this->config['db_passwd']);return $mysql;} catch (PDOException $e) {throw new Exception($e->getMessage());}}/*** 获取数据库连接* @return mixed|null|PDO* @throws Exception*/public function getConnection(){$mysql = null;// 判断是否为空 , 如果池空了 , 判断当前连接数是否下于最大连接数// 如果小于最大连接数创建新连接数if ($this->connections->isEmpty()) {if ($this->count < $this->max) {$this->count++;$mysql = $this->createDb();} else {$mysql = $this->connections->pop($this->timeOut);}} else {$mysql = $this->connections->pop($this->timeOut);}// 获取不到数据库连接抛出异常if (!$mysql) {throw new Exception('没有连接了');}// 当协程结束之后归还连接池defer(function () use ($mysql) {$this->connections->push($mysql);});return $mysql;}/*** 调试打印连接池的容量 , 非主要代码* @param $str*/public function printLenth($str){echo $str . $this->connections->length() . "n";}}


推荐阅读