MySQL.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. <?php
  2. namespace PhpLife\Frame\Library;
  3. use PhpLife\Frame\Logger;
  4. use PhpLife\Frame\Exception;
  5. /**
  6. * 处理MySQL连接、查询和写入
  7. *
  8. * @package PhpLife\Frame\Library
  9. */
  10. class MySQL extends \PhpLife\Frame\Library
  11. {
  12. private static $throwException = false;
  13. // 出错后是否抛出异常
  14. private static $inTransaction = false;
  15. // 是否在事务中
  16. private static $newConnection = false;
  17. // 是否使用新连接
  18. const CONNECTION_TIMEOUT = 21600;
  19. // 每个连接最多可以使用时间,超过这个时间之后创建新连接
  20. protected $exception;
  21. /**
  22. * 执行查询操作
  23. *
  24. * @param string $sql
  25. * @param array $data
  26. * @param bool $isMaster
  27. * @param bool $fetchAll
  28. * @throws \Exception
  29. * @return array|mixed
  30. */
  31. public function read($sql, $data, $isMaster = false, $fetchAll = true)
  32. {
  33. try {
  34. $stmt = $this->getConnection($isMaster)->prepare($sql);
  35. $stmt->execute($data);
  36. $stmt->setFetchMode(\PDO::FETCH_ASSOC);
  37. if ($fetchAll) {
  38. $result = $stmt->fetchAll();
  39. } else {
  40. $result = $stmt->fetch();
  41. }
  42. } catch (\Exception $ex) {
  43. $result = null;
  44. $this->exception = $ex;
  45. $message = array(
  46. 'info' => $ex->getCode() . '=>' . $ex->getMessage() . ';' . json_encode($ex->errorInfo),
  47. 'sql' => $sql,
  48. 'data' => $data
  49. );
  50. Logger::error($message, 'mysql.read.error');
  51. // 如果是失去连接的错误,则进行重试
  52. if (in_array($ex->errorInfo[1], array(
  53. '2006',
  54. '2013'
  55. ))) {
  56. self::$newConnection = true;
  57. return $this->read($sql, $data, $isMaster, $fetchAll);
  58. }
  59. // 处理事务时抛出异常
  60. if (self::$throwException) {
  61. throw new \Exception($ex->getCode() . '=>' . $ex->getMessage(), '90203');
  62. }
  63. }
  64. Logger::debug(array(
  65. 'sql' => $sql,
  66. 'data' => $data,
  67. 'result' => $result
  68. ), 'mysql.read.debug');
  69. return $result;
  70. }
  71. /**
  72. * 执行写入操作
  73. *
  74. * @param string $sql
  75. * @param
  76. * $data
  77. * @throws \Exception
  78. * @return boolean
  79. */
  80. public function write($sql, $data)
  81. {
  82. try {
  83. $stmt = $this->getConnection(true)->prepare($sql);
  84. $stmt->execute($data);
  85. $result = $stmt->rowCount();
  86. } catch (\Exception $ex) {
  87. $result = false;
  88. $this->exception = $ex;
  89. $message = array(
  90. 'info' => $ex->getCode() . '=>' . $ex->getMessage() . ';' . json_encode($ex->errorInfo),
  91. 'sql' => $sql,
  92. 'data' => $data
  93. );
  94. Logger::error($message, 'mysql.write.error');
  95. // 如果是失去连接的错误,则进行重试
  96. if (in_array($ex->errorInfo[1], array(
  97. '2006',
  98. '2013'
  99. ))) {
  100. self::$newConnection = true;
  101. return $this->write($sql, $data);
  102. }
  103. // 处理事务时抛出异常
  104. if (self::$throwException) {
  105. throw new \Exception($ex->getCode() . '=>' . $ex->getMessage(), '90202');
  106. }
  107. }
  108. Logger::debug(array(
  109. 'sql' => $sql,
  110. 'data' => $data,
  111. 'result' => $result
  112. ), 'mysql.write.debug');
  113. return $result;
  114. }
  115. /**
  116. * 事务处理, $callback中如果出错必需抛出异常,否则会真正执行
  117. * 开始时会设置为主动抛出异常,结束时会设置为关闭抛出异常
  118. *
  119. * @param
  120. * $callback
  121. * @param
  122. * $throwException
  123. * @return bool
  124. */
  125. public function transaction($callback, $throwException = false)
  126. {
  127. self::$inTransaction = true; // 处于事务中
  128. self::$throwException = $throwException; // 执行SQL出错时候是否抛出异常
  129. try {
  130. $connection = $this->getConnection(true);
  131. $connection->beginTransaction();
  132. $callback();
  133. $connection->commit();
  134. $result = true;
  135. } catch (\Exception $ex) {
  136. $result = false;
  137. $message = array(
  138. 'info' => $ex->getCode() . '=>' . $ex->getMessage() . ';' . json_encode($ex->errorInfo)
  139. );
  140. Logger::error($message, 'mysql.transaction.error');
  141. // 如果是失去连接的错误,则进行重试
  142. if (in_array($ex->errorInfo[1], array(
  143. '2006',
  144. '2013'
  145. ))) {
  146. self::$newConnection = true;
  147. return $this->transaction($callback, $throwException);
  148. }
  149. try {
  150. $this->getConnection(true)->rollBack();
  151. } catch (\Exception $e) {
  152. // rollback failed
  153. }
  154. if($throwException){
  155. throw new Exception($ex->getMessage(),$ex->getCode());
  156. }
  157. }
  158. self::$throwException = false; // 关闭抛出异常
  159. self::$inTransaction = false; // 事务结束
  160. Logger::debug(array(
  161. 'result' => $result
  162. ), 'mysql.transaction.debug');
  163. return $result;
  164. }
  165. /**
  166. * 获取自增ID
  167. *
  168. * @return string
  169. */
  170. public function getLastInsertId()
  171. {
  172. return $this->getConnection(true)->lastInsertId();
  173. }
  174. /**
  175. * 获取异常
  176. *
  177. * @return mixed|\Exception
  178. */
  179. public function getLastException()
  180. {
  181. return $this->exception;
  182. }
  183. /**
  184. * 获取连接池
  185. * 如果超时时处于事务中,则不主动断开重连
  186. *
  187. * @param bool $isMaster
  188. * @return \PDO
  189. */
  190. protected function getConnection($isMaster = false)
  191. {
  192. static $pool = array();
  193. $connectionName = $this->getFlag($isMaster);
  194. $connectionTimeout = $connectionName . '_timeout';
  195. if (self::$newConnection || ! isset($pool[$connectionName]) || $pool[$connectionName] == false || (self::$inTransaction == false && (time() - $pool[$connectionTimeout] > self::CONNECTION_TIMEOUT))) {
  196. $cfg = $this->getConfig();
  197. if ($isMaster) {
  198. $cfg = $cfg['master'];
  199. } else {
  200. $cfg = $cfg['slave_list'][array_rand($cfg['slave_list'])];
  201. }
  202. $pool[$connectionName] = $this->connect($cfg);
  203. $pool[$connectionTimeout] = time();
  204. self::$newConnection = false;
  205. }
  206. return $pool[$connectionName];
  207. }
  208. /**
  209. * 连接数据库
  210. *
  211. * @param
  212. * $cfg
  213. * @return \PDO
  214. * @throws \Exception
  215. */
  216. protected function connect($cfg)
  217. {
  218. $opts = array(
  219. \PDO::ATTR_AUTOCOMMIT => true,
  220. \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
  221. \PDO::ATTR_TIMEOUT => $cfg['timeout'],
  222. \PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES '" . $cfg['charset'] . "'",
  223. \PDO::ATTR_PERSISTENT => $cfg['persistent']?true : false
  224. ); // 启用长连接
  225. $dsn = 'mysql:dbname=' . $cfg['dbname'] . ';host=' . $cfg['host'] . ';port=' . $cfg['port'];
  226. try {
  227. $db = new \PDO($dsn, $cfg['username'], $cfg['password'], $opts);
  228. } catch (\PDOException $ex) {
  229. $this->exception = $ex;
  230. $message = "MySQL connection failed [" . $dsn . ';' . $cfg['username'] . ';password]' . $ex->getCode() . '=>' . $ex->getMessage();
  231. Logger::emerg($message, 'mysql.connect.error');
  232. throw new \PDOException($message, 90201);
  233. }
  234. $message = "MySQL connect [" . $dsn . ';' . $cfg['username'] . ';password]';
  235. Logger::debug($message, 'mysql.connect.debug');
  236. return $db;
  237. }
  238. }