正在显示
1 个修改的文件
包含
144 行增加
和
34 行删除
| @@ -6,6 +6,7 @@ use Swoole\Database\MysqliConfig; | @@ -6,6 +6,7 @@ use Swoole\Database\MysqliConfig; | ||
| 6 | use Swoole\Database\MysqliPool; | 6 | use Swoole\Database\MysqliPool; |
| 7 | use Swoole\Coroutine; | 7 | use Swoole\Coroutine; |
| 8 | use Swoole\Coroutine\Channel; | 8 | use Swoole\Coroutine\Channel; |
| 9 | +use Jiaoyin\StreamLogger; | ||
| 9 | 10 | ||
| 10 | class MysqlCli | 11 | class MysqlCli |
| 11 | { | 12 | { |
| @@ -18,6 +19,7 @@ class MysqlCli | @@ -18,6 +19,7 @@ class MysqlCli | ||
| 18 | private int $lastReconnectTime = 0; | 19 | private int $lastReconnectTime = 0; |
| 19 | private $connectConfig = null; | 20 | private $connectConfig = null; |
| 20 | private $database = null; | 21 | private $database = null; |
| 22 | + private $logger = null; | ||
| 21 | 23 | ||
| 22 | public function __construct($host, $port, $database, $username, $password, $charset = 'utf8', $prefix = '', $connectCount = 20) | 24 | public function __construct($host, $port, $database, $username, $password, $charset = 'utf8', $prefix = '', $connectCount = 20) |
| 23 | { | 25 | { |
| @@ -34,6 +36,7 @@ class MysqlCli | @@ -34,6 +36,7 @@ class MysqlCli | ||
| 34 | 'connectCount' => $connectCount, | 36 | 'connectCount' => $connectCount, |
| 35 | ]; | 37 | ]; |
| 36 | $this->poolConnect(); | 38 | $this->poolConnect(); |
| 39 | + $this->logger = new StreamLogger(); //默认路径 | ||
| 37 | } | 40 | } |
| 38 | 41 | ||
| 39 | private function poolConnect() | 42 | private function poolConnect() |
| @@ -53,12 +56,17 @@ class MysqlCli | @@ -53,12 +56,17 @@ class MysqlCli | ||
| 53 | $this->prefix = $this->connectConfig['prefix']; | 56 | $this->prefix = $this->connectConfig['prefix']; |
| 54 | } | 57 | } |
| 55 | 58 | ||
| 56 | - //执行sql | ||
| 57 | - public function execute($action, $sql, float $timeout = 5.0, int $retry = 1) | 59 | + public function execute($action, $sql, float $timeout = 5.0, int $retry = 1, bool $assoc = false) |
| 58 | { | 60 | { |
| 59 | - $ret = false; | ||
| 60 | - $db = $this->pool->get(); | 61 | + retry_label: |
| 62 | + $db = null; | ||
| 63 | + $start_time = microtime(true); | ||
| 61 | try { | 64 | try { |
| 65 | + $db = $this->pool->get(); | ||
| 66 | + if (!$db) { | ||
| 67 | + $this->logger->error('mysql', "数据库连接池耗尽,请检查数据库连接配置..."); | ||
| 68 | + throw new \RuntimeException("数据库连接池耗尽"); | ||
| 69 | + } | ||
| 62 | $chan = new Channel(1); | 70 | $chan = new Channel(1); |
| 63 | Coroutine::create(function () use ($chan, $db, $sql) { | 71 | Coroutine::create(function () use ($chan, $db, $sql) { |
| 64 | try { | 72 | try { |
| @@ -69,34 +77,103 @@ class MysqlCli | @@ -69,34 +77,103 @@ class MysqlCli | ||
| 69 | } | 77 | } |
| 70 | }); | 78 | }); |
| 71 | 79 | ||
| 72 | - $result = $chan->pop($timeout); // 超时控制 | 80 | + /** 等待执行结果(支持超时) */ |
| 81 | + $result = $chan->pop($timeout); | ||
| 73 | 82 | ||
| 83 | + /** ---------- 处理超时 ---------- */ | ||
| 74 | if ($result === false) { | 84 | if ($result === false) { |
| 75 | - // 超时 | ||
| 76 | - output("SQL超时: {$sql}, 超时时间 {$timeout}s"); | 85 | + $this->logger->error('mysql', "SQL超时: {$timeout}s, SQL: {$sql}"); |
| 77 | if ($retry > 0) { | 86 | if ($retry > 0) { |
| 78 | - output("超时后自动重试一次: {$sql}"); | ||
| 79 | - $this->pool->put($db); | ||
| 80 | - return $this->execute($action, $sql, $timeout, $retry - 1); | 87 | + $this->logger->error('mysql', "执行超时,SQL:{$sql} 正在销毁连接并重试..."); |
| 88 | + // 1. 永远不要把超时的连接放回池 | ||
| 89 | + if ($db) { | ||
| 90 | + @$db->close(); // 强制关闭 | ||
| 91 | + // 重要:告诉连接池不要再使用这个连接 | ||
| 92 | + $db = null; | ||
| 81 | } | 93 | } |
| 82 | - } elseif ($result instanceof \Throwable) { | ||
| 83 | - output("执行SQL异常: {$sql}"); | ||
| 84 | - output("异常信息: " . $result->getMessage()); | ||
| 85 | - } else { | 94 | + // . 重建一个新连接补入池中 |
| 95 | + $retry--; | ||
| 96 | + goto retry_label; | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + return false; | ||
| 100 | + } | ||
| 101 | + /** ---------- 处理异常(断开 / gone away / server has gone away) ---------- */ | ||
| 102 | + if ($result instanceof \Throwable) { | ||
| 103 | + $msg = $result->getMessage(); | ||
| 104 | + $this->logger->error('mysql', "执行异常: {$msg}; SQL: {$sql}"); | ||
| 105 | + $needReconnect = false; | ||
| 106 | + if ( | ||
| 107 | + stripos($msg, 'gone away') !== false || | ||
| 108 | + stripos($msg, 'Lost connection') !== false || | ||
| 109 | + stripos($msg, 'server has gone away') !== false || | ||
| 110 | + stripos($msg, 'server closed') !== false || | ||
| 111 | + stripos($msg, 'Connection reset') !== false | ||
| 112 | + ) { | ||
| 113 | + $needReconnect = true; | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + if ($needReconnect && $retry > 0) { | ||
| 117 | + $this->logger->error('mysql', "数据库连接断开,SQL:{$sql}自动重连并重试 SQL..."); | ||
| 118 | + if ($db) { | ||
| 119 | + try { | ||
| 120 | + $db->close(); // 强制关闭 | ||
| 121 | + // 重要:告诉连接池不要再使用这个连接 | ||
| 122 | + $db = null; | ||
| 123 | + } catch (\Throwable $e) { | ||
| 124 | + $this->logger->error('mysql', "db close 失败,SQL:{$sql}忽略"); | ||
| 125 | + } | ||
| 126 | + } | ||
| 127 | + $this->poolConnect(); | ||
| 128 | + | ||
| 129 | + $retry--; | ||
| 130 | + goto retry_label; | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + return false; | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + /** ---------- 成功执行 ---------- */ | ||
| 86 | if ($action === 'SELECT') { | 137 | if ($action === 'SELECT') { |
| 87 | - $ret = $result->fetch_all(); | 138 | + if ($assoc) { |
| 139 | + $rows = $result->fetch_all(MYSQLI_ASSOC); | ||
| 88 | } else { | 140 | } else { |
| 89 | - $ret = $result; | 141 | + $rows = $result->fetch_all(); |
| 90 | } | 142 | } |
| 143 | + $result->free(); | ||
| 144 | + return $rows; | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + return $result; | ||
| 148 | + } catch (\Throwable $e) { | ||
| 149 | + $this->logger->error('mysql', "execute() SQL:{$sql}致命异常: " . $e->getMessage()); | ||
| 150 | + if ($retry > 0) { | ||
| 151 | + $this->logger->error('mysql', "致命异常:SQL:{$sql}尝试重连并重试"); | ||
| 152 | + if ($db) { | ||
| 153 | + @$db->close(); // 强制关闭 | ||
| 154 | + $db = null; | ||
| 91 | } | 155 | } |
| 156 | + $retry--; | ||
| 157 | + goto retry_label; | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + return false; | ||
| 92 | } finally { | 161 | } finally { |
| 162 | + $end_time = microtime(true); | ||
| 163 | + $count_time = $end_time - $start_time; | ||
| 164 | + if ($count_time > 0.5) { | ||
| 165 | + $this->logger->log('mysql', "SQL执行时间: " . $count_time . "s, SQL: {$sql}"); | ||
| 166 | + } | ||
| 93 | if ($db) { | 167 | if ($db) { |
| 168 | + try { | ||
| 94 | $this->pool->put($db); | 169 | $this->pool->put($db); |
| 170 | + } catch (\Throwable $e) { | ||
| 171 | + $this->logger->error('mysql', "连接 put 回池失败(可能已断开),SQL:{$sql}忽略"); | ||
| 95 | } | 172 | } |
| 96 | } | 173 | } |
| 97 | - | ||
| 98 | - return $ret; | ||
| 99 | } | 174 | } |
| 175 | + } | ||
| 176 | + | ||
| 100 | //插入数据 | 177 | //插入数据 |
| 101 | public function insert($table, $data) | 178 | public function insert($table, $data) |
| 102 | { | 179 | { |
| @@ -114,14 +191,7 @@ class MysqlCli | @@ -114,14 +191,7 @@ class MysqlCli | ||
| 114 | if (!$sql) { | 191 | if (!$sql) { |
| 115 | return false; | 192 | return false; |
| 116 | } | 193 | } |
| 117 | - $data = $this->execute('SELECT', $sql); | ||
| 118 | - | ||
| 119 | - // 如果没有数据 | ||
| 120 | - if (!$data) return []; | ||
| 121 | - | ||
| 122 | - $newData = []; | ||
| 123 | - | ||
| 124 | - // ✅ 判断是否包含聚合函数 | 194 | + // 判断是否包含聚合函数 |
| 125 | $isAggregate = false; | 195 | $isAggregate = false; |
| 126 | foreach ($col as $c) { | 196 | foreach ($col as $c) { |
| 127 | if (stripos($c, 'sum(') !== false || stripos($c, 'count(') !== false || stripos($c, 'avg(') !== false || stripos($c, 'max(') !== false || stripos($c, 'min(') !== false) { | 197 | if (stripos($c, 'sum(') !== false || stripos($c, 'count(') !== false || stripos($c, 'avg(') !== false || stripos($c, 'max(') !== false || stripos($c, 'min(') !== false) { |
| @@ -133,23 +203,37 @@ class MysqlCli | @@ -133,23 +203,37 @@ class MysqlCli | ||
| 133 | if ($isAggregate && empty($groupBy)) { | 203 | if ($isAggregate && empty($groupBy)) { |
| 134 | // 没有 group by 的单行聚合查询 | 204 | // 没有 group by 的单行聚合查询 |
| 135 | $db = $this->pool->get(); | 205 | $db = $this->pool->get(); |
| 206 | + try { | ||
| 136 | $stmt = $db->query($sql); | 207 | $stmt = $db->query($sql); |
| 137 | $result = $stmt->fetch_assoc(); | 208 | $result = $stmt->fetch_assoc(); |
| 138 | - $this->pool->put($db); | 209 | + $stmt->free(); // 释放结果集,防止Commands out of sync错误 |
| 139 | return [$result]; | 210 | return [$result]; |
| 211 | + } finally { | ||
| 212 | + $this->pool->put($db); | ||
| 213 | + } | ||
| 140 | } elseif ($isAggregate && !empty($groupBy)) { | 214 | } elseif ($isAggregate && !empty($groupBy)) { |
| 141 | // 有 group by 的聚合查询,多行返回 | 215 | // 有 group by 的聚合查询,多行返回 |
| 142 | $db = $this->pool->get(); | 216 | $db = $this->pool->get(); |
| 217 | + try { | ||
| 143 | $stmt = $db->query($sql); | 218 | $stmt = $db->query($sql); |
| 144 | $results = []; | 219 | $results = []; |
| 145 | while ($row = $stmt->fetch_assoc()) { | 220 | while ($row = $stmt->fetch_assoc()) { |
| 146 | $results[] = $row; | 221 | $results[] = $row; |
| 147 | } | 222 | } |
| 148 | - $this->pool->put($db); | 223 | + $stmt->free(); // 释放结果集,防止Commands out of sync错误 |
| 149 | return $results; | 224 | return $results; |
| 225 | + } finally { | ||
| 226 | + $this->pool->put($db); | ||
| 150 | } | 227 | } |
| 228 | + } | ||
| 229 | + | ||
| 230 | + // 非聚合查询,使用execute方法获取数据 | ||
| 231 | + $data = $this->execute('SELECT', $sql); | ||
| 232 | + | ||
| 233 | + // 如果没有数据 | ||
| 234 | + if (!$data) return []; | ||
| 151 | 235 | ||
| 152 | - // ✅ 普通查询才查字段结构 | 236 | + // 普通查询才查字段结构 |
| 153 | if (empty($col)) { | 237 | if (empty($col)) { |
| 154 | $newsql = 'SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "' . $this->database . '" AND TABLE_NAME="' . $this->parseTable($table) . '" ORDER BY `ORDINAL_POSITION` ASC'; | 238 | $newsql = 'SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "' . $this->database . '" AND TABLE_NAME="' . $this->parseTable($table) . '" ORDER BY `ORDINAL_POSITION` ASC'; |
| 155 | $coldata = $this->execute('SELECT', $newsql); | 239 | $coldata = $this->execute('SELECT', $newsql); |
| @@ -196,7 +280,7 @@ class MysqlCli | @@ -196,7 +280,7 @@ class MysqlCli | ||
| 196 | { | 280 | { |
| 197 | $table = $this->parseTable($table); | 281 | $table = $this->parseTable($table); |
| 198 | if (!is_array($data)) { | 282 | if (!is_array($data)) { |
| 199 | - output('insert data 不合法'); | 283 | + $this->logger->error('mysql', 'insert data 不合法'); |
| 200 | return false; | 284 | return false; |
| 201 | } | 285 | } |
| 202 | $keys = array_keys($data); | 286 | $keys = array_keys($data); |
| @@ -216,7 +300,7 @@ class MysqlCli | @@ -216,7 +300,7 @@ class MysqlCli | ||
| 216 | $table = $this->parseTable($table); | 300 | $table = $this->parseTable($table); |
| 217 | 301 | ||
| 218 | if (!empty($col) && !is_array($col)) { | 302 | if (!empty($col) && !is_array($col)) { |
| 219 | - output('select where col 不合法'); | 303 | + $this->logger->error('mysql', 'select where col 不合法'); |
| 220 | return false; | 304 | return false; |
| 221 | } | 305 | } |
| 222 | 306 | ||
| @@ -246,7 +330,7 @@ class MysqlCli | @@ -246,7 +330,7 @@ class MysqlCli | ||
| 246 | $tmp = ''; | 330 | $tmp = ''; |
| 247 | foreach ($where as $key => $value) { | 331 | foreach ($where as $key => $value) { |
| 248 | if (!is_array($value) && !isset($value[1])) { | 332 | if (!is_array($value) && !isset($value[1])) { |
| 249 | - output('where 不合法'); | 333 | + $this->logger->error('mysql', 'where 不合法'); |
| 250 | var_dump($where); | 334 | var_dump($where); |
| 251 | return false; | 335 | return false; |
| 252 | } | 336 | } |
| @@ -265,7 +349,8 @@ class MysqlCli | @@ -265,7 +349,8 @@ class MysqlCli | ||
| 265 | } | 349 | } |
| 266 | $whereTxt .= $tmp; | 350 | $whereTxt .= $tmp; |
| 267 | } else { | 351 | } else { |
| 268 | - output('where 不合法'); | 352 | + $this->logger->error('mysql', 'where 不合法'); |
| 353 | + | ||
| 269 | var_dump($where); | 354 | var_dump($where); |
| 270 | return false; | 355 | return false; |
| 271 | } | 356 | } |
| @@ -277,7 +362,8 @@ class MysqlCli | @@ -277,7 +362,8 @@ class MysqlCli | ||
| 277 | { | 362 | { |
| 278 | $table = $this->parseTable($table); | 363 | $table = $this->parseTable($table); |
| 279 | if (!is_array($data) || empty($data)) { | 364 | if (!is_array($data) || empty($data)) { |
| 280 | - output('更新数据不能为空'); | 365 | + $this->logger->error('mysql', '更新数据不能为空'); |
| 366 | + | ||
| 281 | return false; | 367 | return false; |
| 282 | } | 368 | } |
| 283 | $setTxt = 'set '; | 369 | $setTxt = 'set '; |
| @@ -319,4 +405,28 @@ class MysqlCli | @@ -319,4 +405,28 @@ class MysqlCli | ||
| 319 | } | 405 | } |
| 320 | return $valueNew; | 406 | return $valueNew; |
| 321 | } | 407 | } |
| 408 | + // 删除数据 | ||
| 409 | + public function delete($table, $where = []) | ||
| 410 | + { | ||
| 411 | + $sql = $this->parseDelete($table, $where); | ||
| 412 | + if (!$sql) { | ||
| 413 | + return false; | ||
| 414 | + } | ||
| 415 | + return $this->execute('DELETE', $sql); | ||
| 416 | + } | ||
| 417 | + | ||
| 418 | + // 解析 delete | ||
| 419 | + private function parseDelete($table, $where) | ||
| 420 | + { | ||
| 421 | + $table = $this->parseTable($table); | ||
| 422 | + $whereTxt = $this->parseWhere($where); | ||
| 423 | + | ||
| 424 | + if (empty($whereTxt)) { | ||
| 425 | + $this->logger->error('mysql', 'delete 必须指定 where 条件,防止全表删除'); | ||
| 426 | + return false; | ||
| 427 | + } | ||
| 428 | + | ||
| 429 | + $sql = "delete from {$table} {$whereTxt}"; | ||
| 430 | + return $sql; | ||
| 431 | + } | ||
| 322 | } | 432 | } |
-
请 注册 或 登录 后发表评论