Db.Class.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. <?php
  2. namespace Mall\Framework\Db;
  3. class Db
  4. {
  5. static $queries = 0;
  6. public $master = array(), $slaves = array(), $slave = array(), $slave_key, $sql, $error, $errno;
  7. private $dbh, $dbh_master, $dbh_slave, $foreign_key_checks = 0;
  8. public function __construct($master = array(), $slaves = array())
  9. {
  10. $this->master = $master;
  11. $this->dbh_master = $this->connect($master);
  12. $this->dbh = $this->dbh_master;
  13. if(!empty($slaves)){
  14. $this->slaves = $slaves;
  15. $this->dbh_slave = $this->connect_slave();
  16. $this->dbh = $this->dbh_slave;
  17. }
  18. }
  19. public function __call($method, $args)
  20. {
  21. if (in_array($method, array('errorCode', 'errorInfo', 'getAttribute', 'lastInsertId', 'quote', 'setAttribute'), true)) {
  22. if (in_array($method, array('lastInsertId'))) {
  23. return $this->dbh_master()->$method();
  24. }
  25. if (isset($args[0])) {
  26. return isset($args[1]) ? $this->dbh()->$method($args[0], $args[1]) : $this->dbh()->$method($args[0]);
  27. } else {
  28. return $this->dbh()->$method();
  29. }
  30. }
  31. }
  32. public function beginTransaction($foreign_key_checks = false)
  33. {
  34. try{
  35. $result = $this->dbh_master()->beginTransaction();
  36. if ($result && !$foreign_key_checks) {
  37. $this->foreign_key_checks = intval($this->dbh_master()->query("SELECT @@FOREIGN_KEY_CHECKS")->fetchColumn(0));
  38. $this->foreign_key_checks && $this->dbh_master()->query("SET @@FOREIGN_KEY_CHECKS=0");
  39. }
  40. return $result;
  41. }catch (\PDOException $e){
  42. if ( isset($e->errorInfo) && ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) ) {
  43. $this->closeConnection();
  44. return $this->beginTransaction($foreign_key_checks);
  45. }else{
  46. $msg = $e->getMessage();
  47. $err_msg = "SQL:".$msg;
  48. $this->errno = (int)$e->getCode();
  49. $this->error = $err_msg;
  50. return false;
  51. }
  52. }
  53. }
  54. public function commit()
  55. {
  56. try {
  57. $result = $this->dbh_master()->commit();
  58. if ($this->foreign_key_checks) {
  59. $this->dbh_master()->query("SET @@FOREIGN_KEY_CHECKS=1");
  60. $this->foreign_key_checks = 0;
  61. }
  62. return $result;
  63. }catch (\PDOException $e){
  64. if ( isset($e->errorInfo) && ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) ) {
  65. $this->closeConnection();
  66. return $this->commit();
  67. }else{
  68. $msg = $e->getMessage();
  69. $err_msg = "SQL:".$msg;
  70. $this->errno = (int)$e->getCode();
  71. $this->error = $err_msg;
  72. return false;
  73. }
  74. }
  75. }
  76. public function rollBack()
  77. {
  78. try{
  79. $result = $this->dbh_master()->rollBack();
  80. if ($this->foreign_key_checks) {
  81. $this->dbh_master()->query("SET @@FOREIGN_KEY_CHECKS=1");
  82. $this->foreign_key_checks = 0;
  83. }
  84. return $result;
  85. }catch (\PDOException $e){
  86. if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) {
  87. $this->closeConnection();
  88. return $this->rollBack();
  89. }else{
  90. $msg = $e->getMessage();
  91. $err_msg = "SQL:".$msg;
  92. $this->errno = (int)$e->getCode();
  93. $this->error = $err_msg;
  94. return false;
  95. }
  96. }
  97. }
  98. public function connect($options = array())
  99. {
  100. try {
  101. $dbh = new \PDO($options['driver'] . ':host=' . $options['host'] . ';port=' . $options['port'] . ';dbname=' . $options['dbname'] . ';charset=' . $options['charset'], $options['username'], $options['password'], array(
  102. \PDO::ATTR_PERSISTENT => ($options['pconnect'] ? true : false),)
  103. );
  104. $dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
  105. $dbh->setAttribute(\PDO::ATTR_STRINGIFY_FETCHES, false);
  106. $dbh->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
  107. } catch (\PDOException $e) {
  108. $this->errno = $e->getCode();
  109. $this->error = $e->getMessage();
  110. return false;
  111. }
  112. if ($options['driver'] == 'mysql') {
  113. $dbh->exec("SET character_set_connection='" . $options['charset'] . "',character_set_results='" . $options['charset'] . "',character_set_client='{$options['charset']}'" . ($dbh->query("SELECT version()")->fetchColumn(0) > '5.0.1' ? ",sql_mode=''" : ''));
  114. }
  115. return $dbh;
  116. }
  117. private function connect_slave()
  118. {
  119. $this->slave_key = array_rand($this->slaves);
  120. $this->slave = $this->slaves[$this->slave_key];
  121. $this->dbh_slave = $this->connect($this->slave);
  122. if (!$this->dbh_slave && count($this->slaves) > 1) {
  123. unset($this->slaves[$this->slave_key]);
  124. return $this->connect_slave();
  125. }
  126. return $this->dbh_slave;
  127. }
  128. // https://github.com/walkor/mysql/blob/master/src/Connection.php
  129. // https://my.oschina.net/u/222608/blog/1621402
  130. public function closeConnection(){
  131. $this->dbh = null;
  132. $this->dbh_master = null;
  133. $this->dbh_slave = null;
  134. }
  135. public function exec($statement)
  136. {
  137. try{
  138. return $this->dbh($statement) ? $this->dbh->exec($this->sql) : false;
  139. }catch (\PDOException $e){
  140. if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) {
  141. $this->closeConnection();
  142. return $this->exec($statement);
  143. }else{
  144. $msg = $e->getMessage();
  145. $err_msg = "SQL:".$this->sql." ".$msg;
  146. $this->errno = (int)$e->getCode();
  147. $this->error = $err_msg;
  148. return false;
  149. }
  150. }
  151. }
  152. public function prepare($statement, $driver_options = array())
  153. {
  154. try{
  155. return $this->dbh($statement) ? $this->dbh->prepare($this->sql, $driver_options) : false;
  156. }catch (\PDOException $e){
  157. file_put_contents('/tmp/transaction.log',date('Y-m-d H:i:s').'prepare'.var_export('prepare超时错误了',true).PHP_EOL,FILE_APPEND);
  158. if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) {
  159. $this->closeConnection();
  160. return $this->prepare($statement, $driver_options);
  161. }else{
  162. $msg = $e->getMessage();
  163. $err_msg = "SQL:".$this->sql." ".$msg;
  164. $this->errno = (int)$e->getCode();
  165. $this->error = $err_msg;
  166. file_put_contents('/tmp/transaction.log',date('Y-m-d H:i:s').'prepare'.var_export($e->getMessage(),true).PHP_EOL,FILE_APPEND);
  167. return false;
  168. }
  169. }
  170. }
  171. public function exportQuery($statement)
  172. {
  173. try {
  174. if($this->dbh($statement)){
  175. $this->dbh->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
  176. return $this->dbh->query($this->sql,\PDO::FETCH_ASSOC)->fetchAll();
  177. }else{
  178. return false;
  179. }
  180. }catch (\PDOException $e){
  181. if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) {
  182. $this->closeConnection();
  183. return $this->query($statement);
  184. }else{
  185. $msg = $e->getMessage();
  186. $err_msg = "SQL:".$this->sql." ".$msg;
  187. $this->errno = (int)$e->getCode();
  188. $this->error = $err_msg;
  189. return false;
  190. }
  191. }
  192. }
  193. public function query($statement)
  194. {
  195. try {
  196. return $this->dbh($statement) ? $this->dbh->query($this->sql,\PDO::FETCH_ASSOC)->fetchAll() : false;
  197. }catch (\PDOException $e){
  198. if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) {
  199. $this->closeConnection();
  200. return $this->query($statement);
  201. }else{
  202. $msg = $e->getMessage();
  203. $err_msg = "SQL:".$this->sql." ".$msg;
  204. $this->errno = (int)$e->getCode();
  205. $this->error = $err_msg;
  206. return false;
  207. }
  208. }
  209. }
  210. private function dbh($sql = null)
  211. {
  212. if($sql === null){
  213. $this->sql = null;
  214. }else{
  215. $this->sql = $sql;
  216. }
  217. if (is_null($this->dbh)) {
  218. if (is_null($this->dbh_master)) $this->dbh_master = $this->connect($this->master);
  219. $this->dbh = $this->dbh_master;
  220. }
  221. return $this->dbh;
  222. self::$queries++;
  223. $this->sql = str_replace('#table_', $this->master['prefix'], trim($sql));
  224. if ($this->slaves && is_null($this->dbh_master) && stripos($this->sql, 'select') === 0) {
  225. if (is_null($this->dbh_slave)) $this->dbh_slave = $this->connect_slave();
  226. $this->dbh = $this->dbh_slave;
  227. } else {
  228. if (is_null($this->dbh_master)) $this->dbh_master = $this->connect($this->master);
  229. $this->dbh = $this->dbh_master;
  230. }
  231. return $this->dbh;
  232. }
  233. public function get($sql, $data = array(), $fetch_style = \PDO::FETCH_ASSOC)
  234. {
  235. $this->sql = $sql;
  236. try{
  237. $db = $this->prepare($sql);
  238. if (!$db) return false;
  239. if ($db->execute($data)) {
  240. return $db->fetch($fetch_style);
  241. } else {
  242. $this->errno = $db->errorCode();
  243. $this->error = $db->errorInfo();
  244. return false;
  245. }
  246. }catch (\PDOException $e){
  247. file_put_contents('/tmp/transaction.log',date('Y-m-d H:i:s').'get'.var_export('get超时错误了',true).PHP_EOL,FILE_APPEND);
  248. if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) {
  249. $this->closeConnection();
  250. return $this->get($sql, $data, $fetch_style);
  251. }else{
  252. $msg = $e->getMessage();
  253. $err_msg = "SQL:".$sql." ".$msg;
  254. $this->errno = (int)$e->getCode();
  255. $this->error = $err_msg;
  256. return false;
  257. }
  258. }
  259. }
  260. public function cursor($sth)
  261. {
  262. while($row = $sth->fetch(\PDO::FETCH_ASSOC)) {
  263. yield $row;
  264. }
  265. }
  266. public function exportSelect($sql, $data = array())
  267. {
  268. $this->sql = $sql;
  269. try{
  270. $db = $this->prepare($sql);
  271. if (!$db) return false;
  272. $this->dbh->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
  273. if ($db->execute($data)) {
  274. return $this->cursor($db);
  275. } else {
  276. $this->errno = $db->errorCode();
  277. $this->error = $db->errorInfo();
  278. return false;
  279. }
  280. }catch (\PDOException $e){
  281. if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) {
  282. $this->closeConnection();
  283. return $this->exportSelect($sql, $data);
  284. }else{
  285. $msg = $e->getMessage();
  286. $err_msg = "SQL:".$sql." ".$msg;
  287. echo $err_msg;
  288. $this->errno = (int)$e->getCode();
  289. $this->error = $err_msg;
  290. return false;
  291. }
  292. }
  293. }
  294. public function select($sql, $data = array(), $fetch_style = \PDO::FETCH_ASSOC)
  295. {
  296. $this->sql = $sql;
  297. try{
  298. //V($sql);
  299. $db = $this->prepare($sql);
  300. if (!$db) return false;
  301. if ($db->execute($data)) {
  302. return $db->fetchAll($fetch_style);
  303. } else {
  304. $this->errno = $db->errorCode();
  305. $this->error = $db->errorInfo();
  306. return false;
  307. }
  308. }catch (\PDOException $e){
  309. file_put_contents('/tmp/transaction.log',date('Y-m-d H:i:s').'select'.var_export('select超时错误了',true).PHP_EOL,FILE_APPEND);
  310. if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) {
  311. $this->closeConnection();
  312. return $this->select($sql, $data, $fetch_style);
  313. }else{
  314. $msg = $e->getMessage();
  315. $err_msg = "SQL:".$sql." ".$msg;
  316. $this->errno = (int)$e->getCode();
  317. $this->error = $err_msg;
  318. return false;
  319. }
  320. }
  321. }
  322. public function insert($sql, $data = array(), $multiple = false)
  323. {
  324. $this->sql = $sql;
  325. try{
  326. $db = $this->prepare($sql);
  327. if (!$db) return false;
  328. if (empty($data)) {
  329. if ($db->execute()) {
  330. $insertid = $this->dbh_master->lastInsertId();
  331. return $insertid ? $insertid : true;
  332. } else {
  333. $this->errno = $db->errorCode();
  334. $this->error = $db->errorInfo();
  335. return false;
  336. }
  337. }
  338. if ($multiple) {
  339. $insertids = [];
  340. foreach ($data as $r) {
  341. $this->_bindValue($db, $r);
  342. if ($db->execute()) {
  343. $insertids[] = $this->dbh_master->lastInsertId();
  344. }else{
  345. $this->errno = $db->errorCode();
  346. $this->error = $db->errorInfo();
  347. return false;
  348. }
  349. }
  350. return !empty($insertids) ? $insertids : true;
  351. } else {
  352. $this->_bindValue($db, $data);
  353. if ($db->execute()) {
  354. $insertid = $this->dbh_master->lastInsertId();
  355. return $insertid > 0 ? $insertid : true;
  356. } else {
  357. $this->errno = $db->errorCode();
  358. $this->error = $db->errorInfo();
  359. return false;
  360. }
  361. }
  362. }catch (\PDOException $e){
  363. if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) {
  364. $this->closeConnection();
  365. return $this->insert($sql, $data, $multiple);
  366. }else{
  367. file_put_contents('/tmp/transaction.log',date('Y-m-d H:i:s').'insert'.var_export($data,true).PHP_EOL,FILE_APPEND);
  368. $msg = $e->getMessage();
  369. $err_msg = "SQL:".$sql." ".$msg;
  370. $this->sql = $sql;
  371. $this->errno = (int)$e->getCode();
  372. $this->error = $err_msg;
  373. file_put_contents('/tmp/transaction.log',date('Y-m-d H:i:s').'insert'.var_export($e->getMessage(),true).PHP_EOL,FILE_APPEND);
  374. return false;
  375. }
  376. }
  377. }
  378. public function update($sql, $data = array(), $multiple = false)
  379. {
  380. $this->sql = $sql;
  381. try{
  382. $db = $this->prepare($sql);
  383. if (!$db) return false;
  384. if (empty($data)) {
  385. if ($db->execute()) {
  386. $rowcount = $db->rowCount();
  387. return $rowcount ? $rowcount : true;
  388. } else {
  389. $this->errno = $db->errorCode();
  390. $this->error = $db->errorInfo();
  391. return false;
  392. }
  393. }
  394. if ($multiple) {
  395. foreach ($data as $r) {
  396. $this->_bindValue($db, $r);
  397. if (!$db->execute()) {
  398. $this->errno = $db->errorCode();
  399. $this->error = $db->errorInfo();
  400. return false;
  401. }
  402. }
  403. return true;
  404. } else {
  405. $this->_bindValue($db, $data);
  406. if ($db->execute()) {
  407. $rowcount = $db->rowCount();
  408. return $rowcount ? $rowcount : true;
  409. } else {
  410. $this->errno = $db->errorCode();
  411. $this->error = $db->errorInfo();
  412. return false;
  413. }
  414. }
  415. }catch (\PDOException $e){
  416. if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) {
  417. $this->closeConnection();
  418. return $this->update($sql, $data, $multiple);
  419. }else{
  420. $msg = $e->getMessage();
  421. $err_msg = "SQL:".$sql." ".$msg;
  422. $this->errno = (int)$e->getCode();
  423. $this->error = $err_msg;
  424. return false;
  425. }
  426. }
  427. }
  428. public function replace($sql, $data = array(), $multiple = false)
  429. {
  430. return $this->update($sql, $data, $multiple);
  431. }
  432. public function delete($sql, $data = array())
  433. {
  434. $this->sql = $sql;
  435. try{
  436. $db = $this->prepare($sql);
  437. if (!$db) return false;
  438. if ($db->execute($data)) {
  439. $rowcount = $db->rowCount();
  440. return $rowcount ? $rowcount : true;
  441. } else {
  442. $this->errno = $db->errorCode();
  443. $this->error = $db->errorInfo();
  444. return false;
  445. }
  446. }catch (\PDOException $e){
  447. if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) {
  448. $this->closeConnection();
  449. return $this->delete($sql, $data);
  450. }else{
  451. $msg = $e->getMessage();
  452. $err_msg = "SQL:".$sql." ".$msg;
  453. $this->errno = (int)$e->getCode();
  454. $this->error = $err_msg;
  455. return false;
  456. }
  457. }
  458. }
  459. public function limit($sql, $limit = 0, $offset = 0, $data = array(), $fetch_style = \PDO::FETCH_ASSOC)
  460. {
  461. if ($limit > 0) $sql .= $offset > 0 ? " LIMIT $offset, $limit" : " LIMIT $limit";
  462. return $this->select($sql, $data, $fetch_style);
  463. }
  464. public function page($sql, $page = 1, $size = 20, $data = array(), $fetch_style = \PDO::FETCH_ASSOC)
  465. {
  466. $page = isset($page) ? max(intval($page), 1) : 1;
  467. $size = max(intval($size), 1);
  468. $offset = ($page - 1) * $size;
  469. return $this->limit($sql, $size, $offset, $data, $fetch_style);
  470. }
  471. public function select_db($dbname)
  472. {
  473. return $this->exec("USE $dbname");
  474. }
  475. public function list_fields($table, $field = null)
  476. {
  477. $sql = "SHOW COLUMNS FROM `$table`";
  478. if ($field) $sql .= " LIKE '$field'";
  479. return $this->query($sql);
  480. }
  481. public function list_tables($dbname = null)
  482. {
  483. $tables = array();
  484. $sql = $dbname ? "SHOW TABLES FROM `$dbname`" : "SHOW TABLES";
  485. $result = $this->query($sql);
  486. foreach ($result as $r) {
  487. $tables[] = array_pop($r);
  488. }
  489. return $tables;
  490. }
  491. public function list_dbs()
  492. {
  493. $dbs = array();
  494. $result = $this->query("SHOW DATABASES");
  495. foreach ($result as $r) {
  496. foreach ($r as $db) $dbs[] = $db;
  497. }
  498. return $dbs;
  499. }
  500. public function get_primary($table)
  501. {
  502. $primary = array();
  503. $result = $this->query("SHOW COLUMNS FROM `$table`");
  504. foreach ($result as $r) {
  505. if ($r['Key'] == 'PRI') $primary[] = $r['Field'];
  506. }
  507. return count($primary) == 1 ? $primary[0] : (empty($primary) ? null : $primary);
  508. }
  509. public function get_var($var = null)
  510. {
  511. $variables = array();
  512. $sql = is_null($var) ? '' : " LIKE '$var'";
  513. $result = $this->query("SHOW VARIABLES $sql");
  514. foreach ($result as $r) {
  515. if (!is_null($var) && isset($r['Value'])) return $r['Value'];
  516. $variables[$r['Variable_name']] = $r['Value'];
  517. }
  518. return $variables;
  519. }
  520. public function version()
  521. {
  522. $db = $this->query("SELECT version()");
  523. return $db ? $db->fetchColumn(0) : false;
  524. }
  525. public function prefix()
  526. {
  527. return $this->master['prefix'];
  528. }
  529. public function errno()
  530. {
  531. return is_null($this->errno) ? $this->errorCode() : $this->errno;
  532. }
  533. public function error()
  534. {
  535. if (is_null($this->error)) {
  536. return $this->errorInfo();
  537. } else {
  538. /*if($this->sql && $this->error){
  539. $this->error .= 'sql:'.$this->sql;
  540. }*/
  541. return $this->error;
  542. }
  543. }
  544. /**
  545. * 检查数据库连接,是否有效,无效则重新建立
  546. */
  547. protected function checkConnection()
  548. {
  549. if (!$this->pdo_ping())
  550. {
  551. $this->dbh_master = NULL;
  552. }
  553. }
  554. /**
  555. * 检查连接是否可用
  556. * @return Boolean
  557. */
  558. function pdo_ping(){
  559. file_put_contents('/tmp/transaction.log',date('Y-m-d H:i:s').'ping'.var_export(is_null($this->dbh),true).PHP_EOL,FILE_APPEND);
  560. if( is_null($this->dbh) ){
  561. return false;
  562. }
  563. if (!$this->dbh->getAttribute(\PDO::ATTR_SERVER_INFO)) {
  564. $this->errno = $this->dbh->errorCode();
  565. $this->error = $this->dbh->errorInfo();
  566. file_put_contents('/tmp/transaction.log',date('Y-m-d H:i:s').'error'.var_export($this->errno.':'.$this->error,true).PHP_EOL,FILE_APPEND);
  567. if($this->errno == 'HY000'){
  568. $this->dbh_master = null;
  569. }
  570. return false;
  571. }
  572. return true;
  573. }
  574. private function dbh_master()
  575. {
  576. //$this->checkConnection();
  577. if (is_null($this->dbh_master)) $this->dbh_master = $this->connect($this->master);
  578. $this->dbh = $this->dbh_master;
  579. return $this->dbh;
  580. }
  581. private function _bindValue(& $db, $data)
  582. {
  583. if (!is_array($data)) return false;
  584. foreach ($data as $k => $v) {
  585. $k = is_numeric($k) ? $k + 1 : ':' . $k;
  586. $db->bindValue($k, $v);
  587. }
  588. return true;
  589. }
  590. }