MySQLi.class.php 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354
  1. <?php
  2. namespace KIF\Db;
  3. use KIF\Core\Config;
  4. use MySQLi as MySQLiResource;
  5. use KIF\Debug\Debug;
  6. use KIF\Core\Request;
  7. use KIF\Cookie;
  8. use Exception;
  9. class MySQLi {
  10. /**
  11. * 当前的数据库连接对象
  12. *
  13. * @var MySQLiResource
  14. */
  15. private $link;
  16. /**
  17. * 当前连接的数据库名
  18. * @var string
  19. */
  20. private $dbname;
  21. /**
  22. * #private
  23. * 统计查询次数
  24. *
  25. * @var Int
  26. */
  27. static public $queries = 0;
  28. /**
  29. * 通过$this->query 所执行的总时间
  30. * 只有 debug::sql打开才会统计
  31. *
  32. * @var Int
  33. */
  34. private static $intQueriesTotalTime = 0;
  35. /**
  36. * 保存所有已建立的到服务器的连接。
  37. *
  38. * @var Array 结构如下:
  39. * array(
  40. * '连接唯一标志' => MySQLiResource //连接唯一标志由 $this->getUniqueFlagOfLink() 获取;MySQLiResource 是到主服务器1的连接。
  41. * , ...
  42. * )
  43. */
  44. static private $links;
  45. /**
  46. * 调试等级
  47. * 0 不处理(交予外部程序处理)
  48. * 1 显示错误并中断程序
  49. * 2 直接中断程序
  50. *
  51. * @var Int
  52. */
  53. public $debug_level = 1;
  54. /**
  55. * 最近一次执行的语句
  56. *
  57. * @var string
  58. */
  59. private $last_sql;
  60. /**
  61. * 最后一次执行 query 后获取的 mysqli_result 对象
  62. *
  63. * @var mysqli_result
  64. */
  65. private $result;
  66. /**
  67. * from Data Source Name (dsn)
  68. * for example: mysqli://root:851031@localhost:3306/testDb?charset=utf8
  69. *
  70. * @var Array (
  71. * 'prefix' => '',
  72. * 'host' => '',
  73. * 'port' => '',
  74. * 'user' => '',
  75. * 'pass' => '',
  76. * 'params' => array(),// 通过dsn传递进来的查询字串
  77. * )
  78. */
  79. private $arrDsn;
  80. /**
  81. * 保存当前主服务器连接的信息
  82. *是对 self::$masterDBInfos 数据结构某一项的引用
  83. * @var array
  84. * 结构如下
  85. * array(// 当前集群下的主服务器
  86. * 'host' => //主机名
  87. * , 'port' => //端口
  88. * , 'user' => //用户名
  89. * , 'pass' => //密码
  90. * , 'transactionIds' => //用于存储事务id。Array ('taId1' => true, 'taId2' => false, ...);
  91. * //键代表事务标志,同时也是用于保存点的标志。值表示是否为顶层事务(只能有一个顶层事务)
  92. * , 'isRunOnMaster' => Boolean //$this->query 方法里可检验该值,判断当前查询正连接于主服务器上
  93. * , 'isUseMaster' => Boolean //指示后续查询是否要使用到主库的连接
  94. * , 'arrStatusOfUseMaster' => Array(); //当嵌套修改 isUseMaster 的值时,需要有一个结构保存之前的状态,以便恢复
  95. * )
  96. */
  97. private $masterDBInfo;
  98. /**
  99. * 保存所有用于主服务器连接的信息
  100. *
  101. * @var Array 结构如下:
  102. * array(
  103. * '集群1标志' => array( //
  104. * '主服务器标志' => array(//该标志通过 $this->getUniqueFlagOfLink() 获取:实质是通过 host, port, user, pass 来区别,所以同一集群如果使用不同的用户连接,会有不同的标志
  105. * 'host' => //主机名
  106. * , 'port' => //端口
  107. * , 'user' => //用户名
  108. * , 'pass' => //密码
  109. * , 'transactionIds' => //用于存储事务id。Array ('taId1' => true, 'taId2' => false, ...);
  110. * //键代表事务标志,同时也是用于保存点的标志。值表示是否为顶层事务(只能有一个顶层事务)
  111. * , 'isRunOnMaster' => Boolean //$this->query 方法里可检验该值,判断$this->link是否正链接到主服务器上
  112. * , 'isUseMaster' => Boolean //指示后续查询是否要使用到主库的连接
  113. * , 'arrStatusOfUseMaster' => Array();
  114. * )
  115. * , ...
  116. * )
  117. * , ...
  118. * )
  119. */
  120. static private $masterDBInfos = array();
  121. /**
  122. * 集群信息,即用于候选的从服务器集
  123. * array(
  124. * 'clusterLevel' => (2 表示正常集群、3 表示低效集群)
  125. * , 'Ignore_Check_Master_Patterns' => array()
  126. *
  127. * , '集群1标志' => array( // 该标志通过 $this->getMasterHost() 获取:集群只需要通过主服务器host即可区分
  128. * array(
  129. * 'host' =>
  130. * , 'port' =>
  131. * , 'user' =>
  132. * , 'pass' =>
  133. * )
  134. * , ...
  135. * )
  136. * , ...
  137. * )
  138. * @var Array
  139. */
  140. static private $slaveDBInfos;
  141. /**
  142. * 保存已从集群信息中获取的用于连接从服务器的信息
  143. * @author Gxg <gaoxiaogang@gmail.com>
  144. *
  145. * @var Array 结构如下:
  146. * array(// 当前集群下的从库信息
  147. * 'host' => //主机名
  148. * , 'port' => //端口
  149. * , 'user' => //用户名
  150. * , 'pass' => //密码
  151. * )
  152. */
  153. private $slaveDBInfo;
  154. /**
  155. * 返回当前集群中从服务器的信息
  156. *
  157. * @return false | Array 格式:
  158. * array(
  159. * 'host' => //主机名
  160. * , 'user' => //用户名
  161. * , 'pass' => //密码
  162. * )
  163. */
  164. private function getCurrentSlaveInfo() {
  165. $arrInvalidSlaveInfos = $this->getCurrentInvalidSlaveInfos();
  166. if (isset($this->slaveDBInfo)) {
  167. # 检验是否有效
  168. if (!$arrInvalidSlaveInfos || !in_array($this->slaveDBInfo['host'], $arrInvalidSlaveInfos)) {
  169. return $this->slaveDBInfo;
  170. }
  171. }
  172. $strUniqueFlagOfCluster = $this->getUniqueFlagOfCurrentCluster();
  173. if (!isset(self::$slaveDBInfos[$strUniqueFlagOfCluster])) {
  174. # 不能使用 $this->slaveDBInfo = null,否则引用的 self::$slaveDBInfos 相应的值也会变成 null
  175. return false;
  176. }
  177. # 把 self::$slaveDBInfos 中已经无效的从库踢掉
  178. if ($arrInvalidSlaveInfos) {
  179. foreach (self::$slaveDBInfos[$strUniqueFlagOfCluster] as $key => $arrSlaveInfo) {
  180. $strSlaveHost = $arrSlaveInfo['host'];
  181. if (in_array($strSlaveHost, $arrInvalidSlaveInfos)) {
  182. unset(self::$slaveDBInfos[$strUniqueFlagOfCluster][$key]);
  183. }
  184. }
  185. }
  186. if (count(self::$slaveDBInfos[$strUniqueFlagOfCluster]) == 0) {
  187. return false;
  188. }
  189. $intRandPos = array_rand(self::$slaveDBInfos[$strUniqueFlagOfCluster]);
  190. $this->slaveDBInfo = & self::$slaveDBInfos[$strUniqueFlagOfCluster][$intRandPos];
  191. if (!isset($this->slaveDBInfo['user']) || !isset($this->slaveDBInfo['pass'])) {//从服务器没有提供连接帐户,则使用主服务器的帐户
  192. $this->slaveDBInfo['user'] = $this->masterDBInfo['user'];
  193. $this->slaveDBInfo['pass'] = $this->masterDBInfo['pass'];
  194. }
  195. if (!isset($this->slaveDBInfo['port'])) {
  196. $this->slaveDBInfo['port'] = $this->masterDBInfo['port'];
  197. }
  198. return $this->slaveDBInfo;
  199. }
  200. /**
  201. * 集群中无效的从服务器信息
  202. * 寻找从服务器时,会忽略该结构中列出的主机
  203. * array(
  204. * '集群1标志' => array('从服务器host', '从服务器host', ...)
  205. * , ...
  206. * )
  207. *
  208. * @var Array
  209. */
  210. static private $invalidSlaveInfos;
  211. /**
  212. * 增加无效的从服务器信息
  213. *
  214. * @param String $strHost
  215. * @return true
  216. */
  217. private function addInvalidSlaveInfo($strHost) {
  218. $strUniqueFlagOfCluster = $this->getUniqueFlagOfCurrentCluster();
  219. if (!isset(self::$invalidSlaveInfos[$strUniqueFlagOfCluster])
  220. || !in_array($strHost, self::$invalidSlaveInfos[$strUniqueFlagOfCluster])
  221. ) {
  222. self::$invalidSlaveInfos[$strUniqueFlagOfCluster][] = $strHost;
  223. }
  224. return true;
  225. }
  226. /**
  227. * 获取当前集群的唯一标志(即主服务器host)
  228. *
  229. * @return String
  230. */
  231. private function getUniqueFlagOfCurrentCluster() {
  232. //return "{$this->masterDBInfo['host']}_{$this->masterDBInfo['port']}_{$this->masterDBInfo['user']}";
  233. return "{$this->masterDBInfo['host']}:{$this->masterDBInfo['port']}";
  234. }
  235. /**
  236. * 获取当前集群下无效的从服务器信息
  237. *
  238. * @return false | Array
  239. */
  240. private function getCurrentInvalidSlaveInfos() {
  241. $strUniqueFlagOfCluster = $this->getUniqueFlagOfCurrentCluster();
  242. if (!isset(self::$invalidSlaveInfos[$strUniqueFlagOfCluster])) {
  243. return false;
  244. }
  245. return self::$invalidSlaveInfos[$strUniqueFlagOfCluster];
  246. }
  247. /**
  248. * 设置客户端连接字符集
  249. *
  250. * @param String $charset 连接编码。该参数决定了从各种编码的表里取出数据后,以$strCharset指定的编码返回
  251. * 比如:gbk编码存储的表数据,可以通过指定$strCharset为utf8来返回utf8编码的数据
  252. * @return Boolean true:成功;false:失败.
  253. */
  254. public function setCharset($charset = null) {
  255. if (is_null($charset)) {
  256. if (isset($this->arrDsn['params']['charset']) && $this->arrDsn['params']['charset']) {
  257. $charset = $this->arrDsn['params']['charset'];
  258. } else {
  259. $charset = "utf8";
  260. }
  261. }
  262. return $boolResult = mysqli_set_charset($this->link, $charset);
  263. }
  264. /**
  265. * 取得客户端连接字符集
  266. *
  267. * @return String
  268. */
  269. public function getCharset() {
  270. return $strResult = mysqli_character_set_name($this->link);
  271. }
  272. /**
  273. * 获取当前连接的唯一标志
  274. *
  275. * @return String
  276. */
  277. private function getUniqueFlagOfLink() {
  278. return "{$this->arrDsn['host']}_{$this->arrDsn['port']}_{$this->arrDsn['user']}_{$this->arrDsn['pass']}";
  279. }
  280. /**
  281. * 构造函数
  282. *
  283. * @param String $dsn For Example: mysqli://root:851031@localhost/testDb
  284. */
  285. public function __construct($dsn) {
  286. $arrUrlInfo = parse_url($dsn);
  287. if (!is_array($arrUrlInfo) || !isset($arrUrlInfo['host']) || !isset($arrUrlInfo['user'])
  288. || !isset($arrUrlInfo['pass']) || !isset($arrUrlInfo['path']))
  289. {
  290. $this->_halt("构造参数不正确:{$dsn}");
  291. return false;
  292. //解析出错时的处理
  293. }
  294. $this->arrDsn['host'] = $arrUrlInfo['host'];
  295. $this->arrDsn['user'] = $arrUrlInfo['user'];
  296. $this->arrDsn['pass'] = $arrUrlInfo['pass'];
  297. $dbname = substr($arrUrlInfo['path'], 1);
  298. if (empty($dbname)) {
  299. return $this->_halt('请先设置数据库名', '21');
  300. }
  301. $this->dbname = $dbname;
  302. isset($arrUrlInfo['scheme']) && $this->arrDsn['prefix'] = $arrUrlInfo['scheme'];
  303. if (!isset($arrUrlInfo['port'])) {
  304. $this->arrDsn['port'] = 3306;
  305. } else {
  306. $this->arrDsn['port'] = $arrUrlInfo['port'];
  307. }
  308. # 处理参数
  309. if (isset($arrUrlInfo['query'])) {
  310. parse_str($arrUrlInfo['query'], $this->arrDsn['params']);
  311. } else {
  312. $this->arrDsn['params'] = array();
  313. }
  314. $strUniqueFlagOfCluster = $this->arrDsn['host'];
  315. $strUniqueFlagOfMaster = $this->getUniqueFlagOfLink();
  316. if (!isset(self::$masterDBInfos[$strUniqueFlagOfCluster][$strUniqueFlagOfMaster])) {
  317. self::$masterDBInfos[$strUniqueFlagOfCluster][$strUniqueFlagOfMaster] = array(
  318. 'host' => $this->arrDsn['host']
  319. , 'port' => $this->arrDsn['port']
  320. , 'user' => $this->arrDsn['user']
  321. , 'pass' => $this->arrDsn['pass']
  322. , 'transactionIds' => null
  323. , 'isRunOnMaster' => false
  324. , 'isUseMaster' => false
  325. , 'arrStatusOfUseMaster' => null
  326. );
  327. }
  328. $this->masterDBInfo = & self::$masterDBInfos[$strUniqueFlagOfCluster][$strUniqueFlagOfMaster];
  329. if (!isset(self::$slaveDBInfos)) {
  330. $this->initSlaveDBInfos();
  331. }
  332. }
  333. /**
  334. * 初始化 self::$slaveDBInfos 数据结构
  335. *
  336. * dbslaves 的结构:
  337. * array(
  338. * array(
  339. * 'master' => (string),//对应主库的host值
  340. * 'mixed' => array(//正常从库
  341. * 'mysqld-6.yoka.com',
  342. * '192.168.0.150',
  343. * ... ,
  344. * ),
  345. * 'delay_mixed' => array(//慢速从库,比如用于提供给爬虫、或提供给翻页的比较后面的页面,无需保证特别好的服务
  346. * 'mysqld-12.yoka.com',
  347. * array(
  348. * 'host' => (string),//必须
  349. * 'user' => (string),//必须
  350. * 'pass' => (string),//必须
  351. * 'port' => (int),//非必须
  352. * ),
  353. * ... ,
  354. * ),
  355. * )
  356. * );
  357. *
  358. */
  359. private function initSlaveDBInfos() {
  360. try {
  361. $dbslavesConfig = Config::getInstance()->get('dbslaves');
  362. } catch (Exception $e) {
  363. return false;
  364. }
  365. if (!is_array($dbslavesConfig)) {
  366. return false;
  367. }
  368. $db_cluster_maps = $dbslavesConfig;
  369. if (isset($_SERVER['Cluster_User_Level']) && $_SERVER['Cluster_User_Level'] == 3) {
  370. $cluster_level = 3;
  371. $strType = 'delay_mixed';
  372. } else {
  373. $cluster_level = 2;
  374. $strType = 'mixed';
  375. }
  376. foreach ($db_cluster_maps as $arrClusterInfo) {
  377. if (!isset($arrClusterInfo['master']) || !isset($arrClusterInfo[$strType]) || !is_array($arrClusterInfo[$strType]) || (count($arrClusterInfo[$strType]) == 0)) {
  378. # TODO 记录日志
  379. continue;
  380. }
  381. foreach($arrClusterInfo[$strType] as $mixConnectInfo) {
  382. if (is_string($mixConnectInfo)) {//类似 mysqld-6.verycd.com 的值
  383. self::$slaveDBInfos[$arrClusterInfo['master']][] = array(
  384. 'host' => $mixConnectInfo
  385. , 'port' => null
  386. , 'user' => null
  387. , 'pass' => null
  388. );
  389. } elseif (is_array($mixConnectInfo)) {//如果$_SERVER['DataBase_Cluster_Map'] 提供客启端的密码,请使用以下格式 !
  390. if (isset($mixConnectInfo['host']) && isset($mixConnectInfo['user']) && isset($mixConnectInfo['pass'])) {
  391. $tmpPort = isset($mixConnectInfo['port']) ? $mixConnectInfo['port'] : 3306;
  392. self::$slaveDBInfos[$arrClusterInfo['master']][] = array(
  393. 'host' => $mixConnectInfo['host']
  394. , 'port' => $tmpPort
  395. , 'user' => $mixConnectInfo['user']
  396. , 'pass' => $mixConnectInfo['pass']
  397. );
  398. } else {
  399. // $this->_halt('该集群:' . $arrClusterInfo['master'] . '在$_SERVER[\'DataBase_Cluster_Map\']里提供的连接信息格式错误');
  400. # TODO 记下错误日志
  401. continue;
  402. }
  403. } else {
  404. # TODO 记下错误日志
  405. // $this->_halt('该集群:' . $arrClusterInfo['master'] . '在$_SERVER[\'DataBase_Cluster_Map\']里提供的连接信息格式错误');
  406. continue;
  407. }
  408. }//end foreach
  409. }//end foreach
  410. }
  411. /**
  412. * 获取当前主连接唯一标志
  413. *
  414. * @return String
  415. */
  416. private function getUniqueFlagOfCurrentMaster() {
  417. return "{$this->masterDBInfo['host']}_{$this->masterDBInfo['port']}_{$this->masterDBInfo['user']}_{$this->masterDBInfo['pass']}";
  418. }
  419. /**
  420. * 获取当前集群状态下到主服务器的连接
  421. *
  422. * @return mysqli | false 已有连接,返回该连接(即mysqli对象);false:没有连接
  423. */
  424. private function getCurrentMasterLink() {
  425. $strUniqueFlagOfCurrentMaster = $this->getUniqueFlagOfCurrentMaster();
  426. if (isset(self::$links[$strUniqueFlagOfCurrentMaster])) {
  427. if ($this->isLink(self::$links[$strUniqueFlagOfCurrentMaster])) {
  428. return self::$links[$strUniqueFlagOfCurrentMaster];
  429. } else {
  430. # 曾建立过连接,但中途该连接失效了,应该对此做出处理的。
  431. if (!empty($this->masterDBInfo['transactionIds'])) {
  432. return $this->_halt("到主库的连接已失效,且该主库上存在事务,必须中断!");
  433. }
  434. }
  435. }
  436. return false;
  437. }
  438. /**
  439. * 当前连接是否连到主库
  440. */
  441. private function isRunOnMaster() {
  442. return (boolean) $this->masterDBInfo['isRunOnMaster'];
  443. }
  444. /**
  445. * 获取所有事务信息
  446. * 只有当前集群中主服务器的连接是可用的,才有事务可言
  447. *
  448. * @return false | Array() false:没有到主服务器的连接或者还没开启事务;
  449. */
  450. private function getTransactions() {
  451. if ($this->getCurrentMasterLink()) {
  452. if (!empty($this->masterDBInfo['transactionIds'])) {
  453. return $this->masterDBInfo['transactionIds'];
  454. }
  455. }
  456. return false;
  457. }
  458. /**
  459. * 当前查询是否正运行在主服务器上并且开启了事务
  460. * 该方法在 $this->query 方法里调用才是最有价值的。
  461. *
  462. * @return Boolean true:是;false:否
  463. */
  464. private function isRunOnTransaction() {
  465. if ($this->isRunOnMaster() && $this->getTransactions()) {
  466. return true;
  467. }
  468. return false;
  469. }
  470. private function isLink($link) {
  471. if (!($link instanceof MySQLiResource)) return false;
  472. $sinfo = @mysqli_get_host_info($link);
  473. return !empty($sinfo);
  474. }
  475. private function isReadSql($sql) {
  476. static $r_ops = array('select','show','desc');
  477. $sql = strtolower(trim($sql));
  478. foreach ($r_ops as $op) {
  479. if (strpos($sql,$op)===0) return true;
  480. }
  481. return false;
  482. }
  483. /**
  484. * 连接数据库。这里创建的是真实链接,不会重用已有的链接
  485. *
  486. * @return false | MySQLiResource false:连接失败
  487. */
  488. private function connect() {
  489. // 连接数据库服务器
  490. $objMysqli = mysqli_init();
  491. $connect_rs = mysqli_real_connect($objMysqli, $this->arrDsn['host'], $this->arrDsn['user'], $this->arrDsn['pass']
  492. , null, $this->arrDsn['port'], null
  493. , MYSQLI_CLIENT_COMPRESS);
  494. if (!$connect_rs) {
  495. return false;
  496. }
  497. # 设置字符集的代码
  498. $this->link = $objMysqli;
  499. $this->setCharset();
  500. return $this->link;
  501. }
  502. /**
  503. * 标志当前连接$this->link连接到主库
  504. *
  505. * @return Boolean
  506. */
  507. private function beginRunOnMaster() {
  508. $this->masterDBInfo['isRunOnMaster'] = true;
  509. return true;
  510. }
  511. /**
  512. * 标志当前连接$this->link离开主库
  513. *
  514. * @return Boolean
  515. */
  516. private function endRunOnMaster() {
  517. $this->masterDBInfo['isRunOnMaster'] = false;
  518. return true;
  519. }
  520. /**
  521. * 该方法只应该由 $this->xconnect()调用
  522. *
  523. * @return Boolean false:连接失败
  524. */
  525. private function connectMaster() {
  526. # These codes increase by Gxg <gaoxiaogang@gmail.com>
  527. # 保证到当前集群的主服务器的连接唯一
  528. $this->arrDsn['host'] = $this->masterDBInfo['host'];
  529. $this->arrDsn['port'] = $this->masterDBInfo['port'];
  530. $this->arrDsn['user'] = $this->masterDBInfo['user'];
  531. $this->arrDsn['pass'] = $this->masterDBInfo['pass'];
  532. $objCurrentMasterLink = $this->getCurrentMasterLink();
  533. if ($objCurrentMasterLink) {
  534. $this->link = $objCurrentMasterLink;
  535. } else {
  536. if (false === $this->connect()) {
  537. return false;
  538. }
  539. $strUniqueFlagOfLink = $this->getUniqueFlagOfLink();
  540. self::$links[$strUniqueFlagOfLink] = $this->link;
  541. }
  542. $this->beginRunOnMaster();
  543. return true;
  544. }
  545. /**
  546. * 该方法只应该由 $this->xconnect()调用
  547. *
  548. * @return Boolean false:连接失败
  549. *
  550. */
  551. private function connectSlave() {
  552. $arrCurrentSlaveInfo = $this->getCurrentSlaveInfo();
  553. if ($arrCurrentSlaveInfo) {
  554. $this->arrDsn['host'] = $arrCurrentSlaveInfo['host'];
  555. $this->arrDsn['port'] = $arrCurrentSlaveInfo['port'];
  556. $this->arrDsn['user'] = $arrCurrentSlaveInfo['user'];
  557. $this->arrDsn['pass'] = $arrCurrentSlaveInfo['pass'];
  558. $strUniqueFlagOfLink = $this->getUniqueFlagOfLink();
  559. if (isset(self::$links[$strUniqueFlagOfLink]) && $this->isLink(self::$links[$strUniqueFlagOfLink])) {
  560. $this->link = self::$links[$strUniqueFlagOfLink];
  561. } else {
  562. if(false === $this->connect()) {
  563. return false;
  564. }
  565. self::$links[$strUniqueFlagOfLink] = $this->link;
  566. }
  567. return true;
  568. } else {//还是连主服务器
  569. return $this->connectMaster();
  570. }
  571. }
  572. /**
  573. *
  574. * 重新链接。
  575. * 目前在 mysql 2006错误,并且没有运行事务时才开启该方法
  576. * @return Boolean
  577. */
  578. private function reConnect() {
  579. if (false === $this->connect()) {
  580. return false;
  581. }
  582. $strUniqueFlagOfLink = $this->getUniqueFlagOfLink();
  583. self::$links[$strUniqueFlagOfLink] = $this->link;
  584. return true;
  585. }
  586. /**
  587. * 判断查询是否要使用主服务器
  588. *
  589. * @return Boolean true:使用主;false:使用从
  590. */
  591. private function isUseMaster() {
  592. return (boolean) $this->masterDBInfo['isUseMaster'];
  593. }
  594. /**
  595. * 保存当前状态,并置为$status
  596. *
  597. * @param Boolean $status
  598. * @return String
  599. */
  600. private function changeStatusOfUseMaster($status) {
  601. ($status === true) || $status = false;
  602. # 保存当前状态
  603. $strMasterStatusId = $this->getUniqueMasterStatusId();
  604. $this->masterDBInfo['arrStatusOfUseMaster'][$strMasterStatusId] = $this->masterDBInfo['isUseMaster'];
  605. $this->masterDBInfo['isUseMaster'] = $status;
  606. return $strMasterStatusId;
  607. }
  608. /**
  609. * 开始使用主库
  610. *
  611. * @return String 返回一串标志,供$this->restore 方法使用,用于恢复上一个状态
  612. */
  613. public function beginUseMaster() {
  614. return $this->changeStatusOfUseMaster(true);
  615. }
  616. /**
  617. * 恢复采用 $strMasterStatusId 为句柄保存的上次的状态
  618. *
  619. * @param String $strMasterStatusId
  620. * @return Boolean
  621. *
  622. */
  623. public function restore($strMasterStatusId) {
  624. # 恢复指定状态
  625. if (isset($this->masterDBInfo['arrStatusOfUseMaster'][$strMasterStatusId])) {
  626. $this->masterDBInfo['isUseMaster'] = $this->masterDBInfo['arrStatusOfUseMaster'][$strMasterStatusId];
  627. unset($this->masterDBInfo['arrStatusOfUseMaster'][$strMasterStatusId]);
  628. return true;
  629. }
  630. return false;
  631. }
  632. /**
  633. * 开始使用从库
  634. * 尽量不要使用该接口,除非你明白自己真的需要
  635. *
  636. */
  637. public function beginUseSlave() {
  638. return $this->changeStatusOfUseMaster(false);
  639. }
  640. /**
  641. * 处理连接
  642. *
  643. * @param String $sql
  644. */
  645. protected function xconnect($sql) {
  646. $isUseSlave = $this->isReadSql($sql) && !$this->isUseMaster() ? true : false;
  647. $intConnectErrorNum = 0;//连接出错次数
  648. while(true) {
  649. if ($isUseSlave) {
  650. $isConnect = $this->connectSlave();
  651. } else {
  652. $isConnect = $this->connectMaster();
  653. }
  654. if (!$isConnect) {//连接失败
  655. ++$intConnectErrorNum;
  656. $strMasterHost = $this->masterDBInfo['host'];
  657. if(4 >= $intConnectErrorNum //允许四次重试
  658. && $this->arrDsn['host'] != $strMasterHost //错误不是发生在主服务器上
  659. ) {
  660. $this->addInvalidSlaveInfo($this->arrDsn['host']);
  661. // $this->addErrorLog(self::PARAM_NO_IMPORTANCE_ERROR_DIR, $intConnectErrorNum);
  662. continue;
  663. }
  664. return $this->_halt('服务器连接失败', '01');
  665. }
  666. # 成功就退出循环
  667. break;
  668. }
  669. }
  670. /**
  671. * 执行一条SQL
  672. *
  673. * @param String $sql
  674. * @return resource result
  675. */
  676. public function query($sql) {
  677. # 每条语句都添加注释,方便debug。比如慢查询日志里知道问题出在哪个文件。
  678. $sql .= '/* ' . $_SERVER['HTTP_HOST'] . ' in '.$_SERVER['PHP_SELF'] . ' */';
  679. if (Debug::$open && preg_match('#^\s*select\s#i', $sql)) {
  680. $explain_query = true;
  681. } else {
  682. $explain_query = false;
  683. }
  684. if ($explain_query) {
  685. # 便于查看不使用缓存时的情况
  686. $sql = preg_replace('#select #i', 'select sql_no_cache ', $sql);
  687. }
  688. $this->last_sql = $sql; // 临时加上
  689. $intQueryErrorNum = 0;//查询出错次数
  690. $intSelectErrorNum = 0;//选择数据库出错次数
  691. while (true) {
  692. $this->xconnect($sql);
  693. $isSelect = mysqli_select_db($this->link, $this->dbname);
  694. # 处理选择数据库错误
  695. if (!$isSelect) {
  696. if ($this->isRunOnTransaction()) {
  697. return $this->_halt('进入数据库失败:存在事务,直接停机', '02');
  698. }
  699. ++$intSelectErrorNum;
  700. if ($intSelectErrorNum > 4) {
  701. return $this->_halt('进入数据库失败后,重试多次后仍然失败', '02');
  702. }
  703. // 服务器链接丢失的错误,并且没有运行事务,允许重连
  704. if ($this->errno() == 2006) {
  705. if ($this->reConnect()) {
  706. continue;
  707. }
  708. }
  709. if ($this->isRunOnMaster()) {// 主库重连错误,停机
  710. return $this->_halt('进入数据库失败', '02');
  711. }
  712. // 否则将这台重库置为无效,尝试重连
  713. $this->addInvalidSlaveInfo($this->arrDsn['host']);
  714. continue;
  715. }
  716. if (!$this->isReadSql($sql)) {
  717. // # 如果是写入语句,记录开始时间
  718. // $objProcessTimeOfWriteSqlTime = new ProcessTime();
  719. // $objProcessTimeOfWriteSqlTime->start();
  720. # 如果运行于事务中,记录该语句
  721. if ($this->isRunOnTransaction()) {
  722. $this->masterDBInfo['arrTransactionSqls'][] = $sql;
  723. }
  724. }
  725. $query = mysqli_query($this->link, $sql);
  726. // # 记录慢写入语句
  727. // if (!$this->isReadSql($sql)) {
  728. // if (($runTimeOfWriteSql = $objProcessTimeOfWriteSqlTime->getFinalTime()) > 1) {
  729. // $this->addSlowWriteSqlLog($runTimeOfWriteSql);
  730. // }
  731. // }
  732. # 处理查询错误
  733. if (!$query) {
  734. if ($this->isRunOnTransaction()) {
  735. return $this->_halt('查询数据库失败:存在事务,直接停机', '21');
  736. }
  737. ++$intQueryErrorNum;
  738. if ($intQueryErrorNum > 4) {
  739. return $this->_halt('查询数据库失败后,重试多次后仍然失败', '21');
  740. }
  741. // 服务器链接丢失的错误,并且没有运行事务,允许重连
  742. if (in_array($this->errno(), array(2006, 2013))) {
  743. if ($this->reConnect()) {
  744. continue;
  745. }
  746. }
  747. static $arrConnectErrnos = array(
  748. 1053 //在操作过程中服务器关闭。
  749. , 1030 //从存储引擎中获得错误
  750. , 126 //表损坏
  751. );
  752. if ($this->isRunOnMaster()) {// 主库重连错误,停机
  753. return $this->_halt('查询主数据库失败', '21');
  754. }
  755. // 否则将这台重库置为无效,尝试重连
  756. if(in_array($this->errno(), $arrConnectErrnos)) {//指定的错误号
  757. $this->addInvalidSlaveInfo($this->arrDsn['host']);//由于连接失效导致的查询出错,将这台服务器标记为无效
  758. continue;
  759. }
  760. return $this->_halt('查询数据库失败,不能处理的错误类型', '21');
  761. }
  762. break;
  763. }
  764. # 走到这里,说明成功的执行了写入sql
  765. if ($this->canSetCookieForMasterDBHasWrite($sql)) {// 如果写入语句成功,就写一个保存时间为$tmp_expiration秒的cookie
  766. $tmp_expiration = 2*60;
  767. Cookie::set(KIF_MASTER_DB_HAS_WRITE_COOKIE_KEY, '1', $tmp_expiration);// 设置$tmp_expiration秒的cookie
  768. }
  769. self::$queries++;
  770. if ($explain_query) {
  771. $begin_microtime = Debug::getTime();
  772. self::$intQueriesTotalTime += $begin_microtime;
  773. $explainSql = 'explain ' . $sql;
  774. $equery = mysqli_query($this->link, $explainSql);
  775. $explain = $this->fetch($equery);
  776. $this->freeResult($equery);
  777. Debug::db($this->getLinkDesc(), $this->dbname, $explainSql, Debug::getTime() - $begin_microtime, $explain);
  778. }
  779. if (!$this->isReadSql($sql)) {
  780. $begin_microtime = Debug::getTime();
  781. Debug::db($this->getLinkDesc(), $this->dbname, $sql, Debug::getTime() - $begin_microtime, $query);
  782. }
  783. $this->isRunOnMaster() && $this->endRunOnMaster();
  784. return $query;
  785. }
  786. /**
  787. *
  788. * 判断指定的sql执行后,能否设置后续查询转到主库的cookie
  789. * @param string $sql
  790. * @return boolean
  791. */
  792. private function canSetCookieForMasterDBHasWrite($sql) {
  793. if (Request::isCLI()) {
  794. return false;
  795. }
  796. if ($this->isReadSql($sql)) {
  797. return false;
  798. }
  799. # xhprof_logs表是用来记录慢查询的,没必要因为这个表写入了一次就把后续的请求都转到主库。
  800. if (preg_match('#(xhprof_logs)#', strtolower($sql))) {
  801. return false;
  802. }
  803. return true;
  804. }
  805. public function fetchOne($sql) {
  806. $begin_microtime = Debug::getTime();
  807. $res = $this->query($sql);
  808. if (!$res) {
  809. return false;
  810. }
  811. $result = $this->fetch($res);
  812. $this->freeResult($res);
  813. Debug::db($this->getLinkDesc(), $this->dbname, $this->last_sql, Debug::getTime() - $begin_microtime, $result);
  814. return $result;
  815. }
  816. /**
  817. * 获取连接描述,用于Debug输出
  818. */
  819. private function getLinkDesc() {
  820. $thread_id = mysqli_thread_id($this->link);
  821. return "mysqli://{$this->arrDsn['user']}:{$this->arrDsn['port']}@{$this->arrDsn['host']} (thread_id: {$thread_id})";
  822. }
  823. /**
  824. * 执行一条SQL并返回此查询包含的所有数据(2维数组)
  825. *
  826. * @param string $sql
  827. * @param string $associateKey 如果指定了$associateKey,返回结果以 每条记录的$associateKey字段做数组下标
  828. * @return false | array
  829. */
  830. public function fetchAll($sql, $associateKey = null) {
  831. $begin_microtime = Debug::getTime();
  832. $res = $this->query($sql);
  833. if (!$res) {
  834. return false;
  835. }
  836. $result = array();
  837. if ($associateKey) {
  838. while (true) {
  839. $row = $this->fetch($res);
  840. if (!$row) {
  841. break;
  842. }
  843. if (isset($row[$associateKey])) {
  844. $result[$row[$associateKey]] = $row;
  845. } else {
  846. $result[] = $row;
  847. }
  848. }
  849. } else {
  850. while (true) {
  851. $row = $this->fetch($res);
  852. if (!$row) {
  853. break;
  854. }
  855. $result[] = $row;
  856. }
  857. }
  858. $this->freeResult($res);
  859. Debug::db($this->getLinkDesc(), $this->dbname, $this->last_sql, Debug::getTime() - $begin_microtime, $result);
  860. return $result;
  861. }
  862. /**
  863. * 执行SQL语句并返回第一行第一列
  864. *
  865. * @param string $sql
  866. * @return false | scala
  867. */
  868. public function fetchSclare($sql) {
  869. $begin_microtime = Debug::getTime();
  870. $result = $this->fetchOne($sql);
  871. if (!$result) {
  872. return false;
  873. }
  874. $result = array_shift($result);
  875. Debug::db($this->getLinkDesc(), $this->dbname, $this->last_sql, Debug::getTime() - $begin_microtime, $result);
  876. return $result;
  877. }
  878. /**
  879. * 返回上一步 INSERT 查询中产生的 AUTO_INCREMENT 的 ID 号;
  880. * 或者 返回 update 语句中 last_insert_id()函数中表达式的值。
  881. *
  882. * !!请记住,一定紧接在insert 或 update 语句后执行该方法,否则$this->link可能已经指向别的服务器了
  883. *
  884. * @return int | NULL >0:成功取到;0:没取到;NULL:$this->link无效
  885. */
  886. public function insertId() {
  887. return mysqli_insert_id($this->link);
  888. }
  889. /**
  890. * $this->insertId() 的别名
  891. * !!请记住,一定紧接在insert 或 update 语句后执行该方法,否则$this->link可能已经指向别的服务器了
  892. * @return int | NULL >0:成功取到;0:没取到;NULL:$this->link无效
  893. */
  894. public function getLastInsertId() {
  895. return $this->insertId();
  896. }
  897. /**
  898. * 返回最近一次 INSERT,UPDATE 或 DELETE 查询所影响的记录行数。
  899. * @return int | null 返回值 >= 0:成功;等于 -1:最后一条查询错误;null:$this->link无效
  900. **/
  901. public function affectedRows()
  902. {
  903. return mysqli_affected_rows($this->link);
  904. }
  905. public function fetch($query, $resulttype = MYSQLI_ASSOC) {
  906. return mysqli_fetch_array($query, $resulttype);
  907. }
  908. protected function freeResult($query) {
  909. return mysqli_free_result($query);
  910. }
  911. /**
  912. * 生成唯一的字符串作为事务的唯一id
  913. *
  914. * @return String
  915. */
  916. static private function getUniqueTransactionId() {
  917. return self::getUniqueId('TAId');
  918. }
  919. /**
  920. * 生成唯一的字符串作为保存当前主服务器状态的唯一id
  921. *
  922. * @return String
  923. */
  924. static private function getUniqueMasterStatusId() {
  925. return self::getUniqueId('MSId');
  926. }
  927. /**
  928. * 生成唯一id
  929. *
  930. * @param String $prefix
  931. * @return String
  932. */
  933. static private function getUniqueId($prefix = '') {
  934. if(!is_string($prefix)) {
  935. $prefix = '';
  936. }
  937. return uniqid($prefix . '_'.rand());
  938. }
  939. /**
  940. * 返回上一个错误文本,如果没有出错则返回 ''(空字符串)。
  941. * 如果没有指定连接资源号,则使用上一个成功打开的连接从数据库服务器提取错误信息。
  942. *
  943. * @return String
  944. */
  945. public function error() {
  946. return @mysqli_error($this->link);
  947. }
  948. /**
  949. * 返回上一个错误号
  950. *
  951. * @return int | NULL
  952. */
  953. public function errno() {
  954. return @mysqli_errno($this->link);
  955. }
  956. /**
  957. * 返回上一个连接错误
  958. *
  959. * @return String
  960. */
  961. public function connect_error() {
  962. return @mysqli_connect_error();
  963. }
  964. /**
  965. * 返回上一个连接的错误号
  966. *
  967. * @return int
  968. */
  969. public function connect_errno() {
  970. return @mysqli_connect_errno();
  971. }
  972. /**
  973. * 根据 $this->debug_level 处理一些异常情况
  974. * 1 直接输出错误信息并中断程序
  975. * 2 直接中断程序
  976. * 其他情况不处理错误,返回flase,修改错误代号和本函数所提供的错误信息,最后的是MySQL服务器提供的信息
  977. *
  978. * @param String $msg
  979. * @param String $errorcode
  980. * @return Array
  981. */
  982. function _halt($msg, $errorcode = '00') {
  983. switch ($this->debug_level) {
  984. case 1:
  985. ob_clean();
  986. header("HTTP/1.0 500 Server Error");
  987. header("Expires: ".gmdate("D, d M Y H:i:s", time())." GMT");
  988. header("Last-Modified: ".gmdate("D, d M Y H:i:s", time())." GMT");
  989. header("Cache-Control: private");
  990. // $out = file_get_contents(ROOT_PATH . '/mysql.html');
  991. $out = '$the_error';
  992. $out = str_replace('$the_error', $msg.'<hr />'.$this->error().' No.'.$this->errno()."<!-- {$this->last_sql} -->", $out);
  993. // $this->addErrorLog(null, null, $msg);
  994. echo $out;
  995. exit;
  996. break;
  997. case 2:
  998. ob_clean();
  999. header("HTTP/1.0 500 Server Error");
  1000. header("Expires: ".gmdate("D, d M Y H:i:s", time())." GMT");
  1001. header("Last-Modified: ".gmdate("D, d M Y H:i:s", time())." GMT");
  1002. header("Cache-Control: private");
  1003. echo $this->connect_error(), "<br />";
  1004. echo $this->connect_errno(), "<br />";
  1005. echo "{$msg}<br />";
  1006. exit('MySQL.');
  1007. break;
  1008. default:
  1009. $this->errorcode = array($errorcode, $msg.':'.$this->last_sql, $this->errno().": ".$this->error());
  1010. return false;
  1011. break;
  1012. }
  1013. }
  1014. ########### TRANSACTION ##########
  1015. /**
  1016. * 开启事务
  1017. *
  1018. * @return false | string false:失败;string:成功返回事务标志
  1019. */
  1020. public function startTransaction() {
  1021. $strTransactionId = self::getUniqueTransactionId();
  1022. if ($this->getTransactions()) {//已存在事务
  1023. if ($this->setSavePoint($strTransactionId)) {
  1024. $this->masterDBInfo['transactionIds'][$strTransactionId] = false;
  1025. return $strTransactionId;
  1026. }
  1027. } else {
  1028. # 初次开启事务
  1029. if (true === $this->query('START TRANSACTION;')) {//如果没有建立到当前主服务器的连接,该操作会隐式的建立
  1030. $this->masterDBInfo['transactionIds'][$strTransactionId] = true;
  1031. return $strTransactionId;
  1032. }
  1033. }
  1034. # 开启事务失败。返回一个独特的字符串,该字符串是不可能出现在 事务id数组中的
  1035. return false;
  1036. }
  1037. /**
  1038. * 回滚父事务
  1039. *
  1040. * @param String $strTransactionId
  1041. * @return Boolean
  1042. */
  1043. private function _rollbackRootTransaction($strTransactionId) {
  1044. if ($this->isRootTransaction($strTransactionId)) {//父事务
  1045. $this->masterDBInfo['transactionIds'] = null;
  1046. $this->masterDBInfo['arrTransactionSqls'] = array();
  1047. return $this->query('ROLLBACK;');
  1048. }
  1049. return false;
  1050. }
  1051. /**
  1052. * 回滚子事务
  1053. *
  1054. * @param String $strTransactionId
  1055. * @return Boolean
  1056. */
  1057. private function _rollbackSubTransaction($strTransactionId) {
  1058. if($this->isSubTransaction($strTransactionId)) {//子事务
  1059. $boolStatusTmp = $this->rollbackToSavePoint($strTransactionId);
  1060. $this->releaseSavePoint($strTransactionId);
  1061. unset($this->masterDBInfo['transactionIds'][$strTransactionId]);
  1062. return $boolStatusTmp;
  1063. }
  1064. return false;
  1065. }
  1066. /**
  1067. * 撤消指定事务
  1068. *
  1069. * @param String $strTransactionId
  1070. * @return Bollean true:成功;false:失败
  1071. */
  1072. public function rollback($strTransactionId) {
  1073. if ($this->isRootTransaction($strTransactionId)) {//父事务
  1074. return $this->_rollbackRootTransaction($strTransactionId);
  1075. } elseif ($this->isSubTransaction($strTransactionId)) {//子事务
  1076. return $this->_rollbackSubTransaction($strTransactionId);
  1077. } else {
  1078. return false;
  1079. }
  1080. }
  1081. /**
  1082. * 提交父事务
  1083. *
  1084. * @param String $strTransactionId
  1085. * @return Boolean
  1086. */
  1087. private function _commitRootTransaction($strTransactionId) {
  1088. if ($this->isRootTransaction($strTransactionId)) {//父事务
  1089. $this->masterDBInfo['transactionIds'] = null;
  1090. $this->masterDBInfo['arrTransactionSqls'] = array();
  1091. return $this->query('COMMIT;');
  1092. }
  1093. return false;
  1094. }
  1095. /**
  1096. * 提交子事务
  1097. *
  1098. * @param String $strTransactionId
  1099. * @return Boolean
  1100. */
  1101. private function _commitSubTransaction($strTransactionId) {
  1102. if ($this->isSubTransaction($strTransactionId)) {//子事务
  1103. $this->releaseSavePoint($strTransactionId);
  1104. unset($this->masterDBInfo['transactionIds'][$strTransactionId]);
  1105. return true;
  1106. }
  1107. return false;
  1108. }
  1109. /**
  1110. * 提交指定事务
  1111. *
  1112. * @param String $strTransactionId
  1113. * @return Boolean true:成功;false:失败
  1114. */
  1115. public function commit($strTransactionId) {
  1116. if ($this->isRootTransaction($strTransactionId)) {//父事务
  1117. return $this->_commitRootTransaction($strTransactionId);
  1118. } elseif ($this->isSubTransaction($strTransactionId)) {//子事务
  1119. return $this->_commitSubTransaction($strTransactionId);
  1120. } else {
  1121. return false;
  1122. }
  1123. }
  1124. /**
  1125. * 设置子事务的保存点,用于支持子事务的回滚
  1126. *
  1127. * @param String $SPId 应该被传递的值是事务的唯一id,即调用self::getUniqueTransactionId()生成的
  1128. * @return Boolean true:成功;false:失败
  1129. */
  1130. private function setSavePoint($SPId) {
  1131. if (true === $this->query("SAVEPOINT {$SPId}")) {
  1132. return true;
  1133. }
  1134. return false;
  1135. }
  1136. /**
  1137. * 获取指定事务的类型(父事务 还是 子事务)
  1138. * 只有当前集群中主服务器的连接是可用的,才有事务可言
  1139. *
  1140. * @param String $strTransactionId
  1141. * @return Boolean | null true:父事务;false:子事务;null:无效
  1142. */
  1143. private function getTransactionTypeById($strTransactionId) {
  1144. if ($this->getCurrentMasterLink()) {
  1145. if (isset($this->masterDBInfo['transactionIds'][$strTransactionId])) {
  1146. return $this->masterDBInfo['transactionIds'][$strTransactionId];
  1147. }
  1148. }
  1149. return null;
  1150. }
  1151. /**
  1152. * 是否父事务
  1153. *
  1154. * @param String $strTransactionId
  1155. * @return Boolean true:是;false:否
  1156. */
  1157. private function isRootTransaction($strTransactionId) {
  1158. if (true === $this->getTransactionTypeById($strTransactionId)) {
  1159. return true;
  1160. }
  1161. return false;
  1162. }
  1163. /**
  1164. * 是否子事务
  1165. *
  1166. * @param String $strTransactionId
  1167. * @return Boolean true:是;false:否
  1168. */
  1169. private function isSubTransaction($strTransactionId) {
  1170. if (false === $this->getTransactionTypeById($strTransactionId)) {
  1171. return true;
  1172. }
  1173. return false;
  1174. }
  1175. /**
  1176. * 回滚到指定事务点
  1177. *
  1178. * @param String $SPId
  1179. * @return Boolean true:成功;false:失败
  1180. */
  1181. private function rollbackToSavePoint($SPId) {
  1182. # 只对子事务的回滚点操作
  1183. if ($this->isSubTransaction($SPId)) {
  1184. if (true === $this->query("ROLLBACK TO SAVEPOINT {$SPId}")) {
  1185. return true;
  1186. }
  1187. }
  1188. return false;
  1189. }
  1190. /**
  1191. * 释放事务保存点
  1192. *
  1193. * @param String $SPId
  1194. * @return Boolean true:成功;false:失败
  1195. */
  1196. private function releaseSavePoint($SPId) {
  1197. # 只对子事务的回滚点操作
  1198. if ($this->isSubTransaction($SPId)) {
  1199. if (true === $this->query("RELEASE SAVEPOINT {$SPId}")) {
  1200. return true;
  1201. }
  1202. }
  1203. return false;
  1204. }
  1205. }