MySQL binlog_format解析后筛选出指定SQL语句/支持远程自动解析,支持伪SQL转换,支持row、mixed、statement模式(原创)

2019-11-26 15:03:23   工作备份

  伪SQL,binlog,row  

  1. <?php
  2. header("Content-Type:text/html; charset=utf-8");
  3. class mysql_analysis
  4. {
  5. protected $file;//binlog 解析后的伪sql文件
  6. protected $mysqli;//MySQL连接句柄
  7. protected $table;//表结构保存
  8. protected $primaryKey;//表主键
  9. protected $database;//当前数据库
  10. protected $lines;//文件总行数
  11. protected $outFile;//SQL写入文件
  12. protected $key;//行数计数器,用于统计进度
  13. protected $searchTable = '';//指定表筛选
  14. protected $exceptTable = '';//排除指定表
  15. protected $type;//操作类型
  16. protected $startTime;//程序开始时间戳
  17. protected $sqlNums = 0;//已解析出的SQL数量
  18. protected $rollback = false;//回滚SQL
  19. protected $rk = false;//insert去除主键
  20. protected $mysqlbinlog;
  21. protected $startDatetTime;
  22. protected $stopDateTime;
  23. public function __construct()
  24. {
  25. set_time_limit(0);//不限制脚本执行时间
  26. $param_arr = getopt("m:h:u:d:p:",
  27. ["port:", "output::", "help::", "table::", "type::", "except::", "rollback::", "rk::","start-datetime:","stop-datetime:","mysqlbinlog:"]);
  28. $info = <<<EOF
  29. example1(本地解析示例): php mysql_analysis.php -h"10.0.108.58" -ubee -dbee -pzxzdapp666 -maa.txt --output=aa.sql --table=wallet/user_role/jobs --except=wallet/user_role --port=3306
  30. example2(远程解析示例):php mysql_analysis.php -h"10.0.108.58" -ubee -dbee-master -pzxzdapp666 --output=drop.sql --start-datetime="2019-11-29 00:00:00" --stop-datetime="2019-11-29 17:00:00" --except=jobs --mysqlbinlog=/usr/local/mysql-5.7.25-macos10.14-x86_64/bin/mysqlbinlog
  31. 注:此脚本不指定-m情况下,需要执行shell_exec函数,请注意放行
  32. -m 指定binlog解析出的分析文件(mysqlbinlog mysql-bin.* --base64-output=decode-rows -vv >> xxxx.txt 解析后的文件,目前支持row/statement模式,理论支持MIXED),不指定-m则表示使用远程获取
  33. -h 指定数据库连接地址
  34. -u 指定用户名
  35. -p 指定数据库密码
  36. -d 指定数据库
  37. --port= 指定数据库端口 可选
  38. --mysqlbinlog 指定系统mysqlbinlog绝对地址(远程获取binlog时必选,指定-m时无需指定)
  39. --output= 指定输出文件,不指定则自动生成一个文件(可选)
  40. --table= 指定表,多表用/隔开,默认全库(可选)
  41. --except= 排除指定表,多表用/隔开,默认无(可选)
  42. --type= insert/updete/delete/alter/drop/create,多类型用/隔开 指定操作类型(可选)
  43. --rollback 执行回滚操作,生成反向sql,row模式支持insert/delete/updatestatement只支持回滚insert
  44. --start-datetime= 用于没有指定-m参数情况下,远程获取binlog的起始时间(可选)
  45. --stop-datetime= 用于没有指定-m参数情况下,远程获取binlog的结束时间 (可选)
  46. --rk 去除insert语句主键 (可选,默认不去除)
  47. --help 查看帮助
  48. EOF;
  49. if (isset($param_arr['help'])) {
  50. $this->log($info . "\n", true);
  51. }
  52. $this->log("\033[0;33;1m".$info . "\n");
  53. if (isset($param_arr['mysqlbinlog']) && $param_arr['mysqlbinlog']) {
  54. $this->mysqlbinlog = $param_arr['mysqlbinlog'];
  55. }
  56. if (!isset($param_arr['h'])) {
  57. $this->log("请使用-h指定数据库连接地址", true);
  58. }
  59. if (!isset($param_arr['u'])) {
  60. $this->log("请使用-u指定用户名", true);
  61. }
  62. if (!isset($param_arr['p'])) {
  63. $this->log("请使用-p指定密码", true);
  64. }
  65. if (!isset($param_arr['d'])) {
  66. $this->log("请使用-d指定数据库", true);
  67. }
  68. if (!isset($param_arr['output']) || (isset($param_arr['output']) && !$param_arr['output'])) {
  69. $param_arr['output'] = "analysis-" . date("Y-m-d H:i:s") . ".sql";
  70. }
  71. if (isset($param_arr['rollback'])) {
  72. //是否开启回滚SQL程序
  73. $this->rollback = true;
  74. }
  75. if (isset($param_arr['rk'])) {
  76. //是否去除主键
  77. $this->rk = true;
  78. }
  79. //指定输出文件
  80. $this->outFile = fopen($param_arr['output'], 'w+');
  81. $this->log("SQL文件保存路径:\e[0;31;1m".$param_arr['output']);
  82. $this->database = $param_arr['d'];
  83. if (isset($param_arr['table']) && $param_arr['table']) {
  84. //指定表
  85. $this->searchTable = explode("/", strtolower($param_arr['table']));
  86. }
  87. if (isset($param_arr['except']) && $param_arr['except']) {
  88. //排除指定表
  89. $this->exceptTable = explode("/", strtolower($param_arr['except']));
  90. }
  91. if ($this->searchTable && $this->exceptTable) {
  92. //同时设置了,指定表和排除表,取两数组差集
  93. $this->searchTable = array_diff($this->searchTable, $this->exceptTable);
  94. }
  95. if (isset($param_arr['type'])) {
  96. //指定操作类型(insert、update、delete)
  97. $this->type = explode("/", strtoupper($param_arr['type']));
  98. }
  99. if (isset($param_arr['start-datetime']) && isset($param_arr['stop-datetime']) && $param_arr['start-datetime'] && $param_arr['stop-datetime']) {
  100. //指定binlog 起始结束时间
  101. $this->startDatetTime = $param_arr['start-datetime'];
  102. $this->stopDateTime = $param_arr['stop-datetime'];
  103. }
  104. $this->file = isset($param_arr['m'])?$param_arr['m']:"";
  105. $this->log("指定操作类型:\033[0;31;1m".($this->type?implode("/",$this->type):"全部"));
  106. $this->log("指定表:\e[0;31;1m".($this->searchTable?implode("/",$this->searchTable):"全部"));
  107. $this->log("排除指定表:\e[0;31;1m".($this->exceptTable?implode("/",$this->exceptTable):"无"));
  108. $this->log("insert语句去除主键:\e[0;31;1m".($this->rk?"去除":"不去除"));
  109. $this->log("是否生成回滚SQL:\e[0;31;1m".($this->rollback?"生成":"不生成"));
  110. //连接数据库=》用于获取表结构
  111. $this->mysqlConnect($param_arr['h'], $param_arr['u'], $param_arr['p'], $param_arr['d'],
  112. isset($param_arr['port']) ? $param_arr['port'] : "3306");
  113. if (!file_exists($this->file)) {
  114. $this->log("文件不存在", true);
  115. }
  116. //获取所有行数
  117. $this->getAllLines();
  118. }
  119. /**
  120. * 连接数据库
  121. * @param $host
  122. * @param $user
  123. * @param $password
  124. * @param $dataBase
  125. * @param int $port
  126. */
  127. public function mysqlConnect($host, $user, $password, $dataBase, $port = 3306)
  128. {
  129. $this->mysqli = new mysqli($host, $user, $password, $dataBase, $port);
  130. if ($info = $this->mysqli->connect_errno) {
  131. $this->log($this->mysqli->connect_error, true);
  132. }
  133. $this->getBinlog($host,$user,$password,$dataBase,$port);
  134. }
  135. /**
  136. * 获取远程binlog
  137. * @param $host
  138. * @param $user
  139. * @param $password
  140. * @param $dataBase
  141. * @param $port
  142. */
  143. public function getBinlog($host,$user,$password,$dataBase,$port){
  144. if(!is_dir("result")){
  145. mkdir("result");
  146. }
  147. if (!$this->file) {
  148. if(!$this->mysqlbinlog){
  149. $this->log("请指定mysqlbinlog绝对地址",true);
  150. }
  151. $info = $this->mysqli->query("show global variables like '%binlog_format%';");
  152. $binlog = $this->mysqli->query("show binary logs");
  153. if ($info && ($info = $info->fetch_object()) && $binlog && $binInfo = $binlog->fetch_all()) {
  154. $mode = $info->Value;
  155. if(!isset($binInfo[0][0])){
  156. $this->log("读取远程binlog 失败,请尝试手动拉取binlog日志,并使用-m指定文件开始解析",true);
  157. }
  158. $time = time();
  159. $shell = $this->mysqlbinlog." --read-from-remote-server -h$host -u$user -p$password -P$port ".$binInfo[0][0];
  160. if (!in_array(strtolower($mode), array("mixed", "statement"))) {
  161. //row模式
  162. $shell.=" --base64-output=DECODE-ROWS -vv";
  163. }
  164. if($this->startDatetTime){
  165. $shell.=" --start-datetime=\"".strval($this->startDatetTime)."\"";
  166. }
  167. if($this->stopDateTime){
  168. $shell.=" --stop-datetime=\"".strval($this->stopDateTime)."\"";
  169. }
  170. $shell.=" --to-last-log --result-file=result/binlog.log";
  171. $this->log("开始获取远程binlog,起始log:".$binInfo[0][0]."\n指定开始时间:".($this->startDatetTime?$this->startDatetTime:"无")."\n指定结束时间:".($this->stopDateTime?$this->stopDateTime:"无"));
  172. shell_exec($shell);
  173. $this->log("binlog获取完成,耗时:".(time()-$time)."s");
  174. if(file_exists("result/binlog.log") && filemtime("result/binlog.log")>$time){
  175. $this->file = "result/binlog.log";
  176. }else{
  177. $this->log("读取远程binlog 失败,请尝试手动拉取binlog日志,并使用-m指定文件开始解析",true);
  178. }
  179. }
  180. }
  181. }
  182. /**
  183. * 分析获取SQL结构体
  184. * @return Generator
  185. */
  186. public function dealFile()
  187. {
  188. $inTable = false;
  189. $sql = '';
  190. $is_row = true;
  191. $database = "";
  192. $id = 0;
  193. foreach ($this->readFile() as $n => $line) {
  194. if ($is_row == true) {
  195. //row模式文件
  196. if (preg_match("/^#{3}\s+(INSERT INTO|UPDATE|DELETE FROM).*/", $line, $match)) {
  197. //匹配到SQL,开始拼接
  198. $sql = preg_replace("/^#{3}\s+/", '', $match[0]);
  199. $inTable = true;
  200. } else {
  201. if ($inTable && preg_match("/^(#{3}\s+|\s+|@\d+).*/", $line, $match)) {
  202. $sql .= preg_replace("/^#{3}\s+/", ' ', $line);
  203. } else {
  204. if ($inTable) {
  205. $sql = preg_replace("/\/\*(.*)+\*\//", '', $sql);
  206. yield "row" => $sql;
  207. $inTable = false;
  208. }
  209. }
  210. }
  211. }
  212. //非row模式
  213. if (preg_match("/^\s*SET INSERT_ID=\s*(\d+)/i", $line, $match)) {
  214. $id = $match[1];
  215. }
  216. if (preg_match("/^(\s*use\s*[`a-zA-Z\-\._]+)/i", $line, $match)) {
  217. $database = preg_replace("/^(\s*use\s*)/", '', $match[0]);
  218. }
  219. if (preg_match("/^\s*(INSERT INTO|UPDATE|DELETE FROM|ALTER TABLE|DROP|CREATE).*/i", $line, $match)) {
  220. $is_row = false;
  221. $inTable = true;
  222. if (preg_match("/^\s*(INSERT INTO)\s+([`\-_a-zA-Z\.]+)/i", $line, $sqlInfo)) {
  223. //拼主键
  224. $primaryKey = $this->getPrimaryKey($database . "." . $sqlInfo[2]);
  225. if ($primaryKey && $id) {
  226. $sql = preg_replace("/^(\s*INSERT\sINTO\s[`_\-a-zA-Z]+\s*\()/i", "$1 `$primaryKey`,",
  227. $match[0]);
  228. $sql = preg_replace("/(values?\s*\()/i", "$1 $id,", $sql);
  229. }
  230. } else {
  231. $sql = preg_replace("/\s*(\/\*).*$/", '', $match[0]);//去除注释
  232. }
  233. } else {
  234. if ($inTable && !$is_row && !preg_match("/^\/\*/", $line, $match)) {
  235. $sql .= $line;
  236. } else {
  237. if (!$is_row && $inTable) {
  238. //非row模式SQL直接输出,不需要再次进行处理
  239. if (preg_match("/(INSERT INTO|UPDATE|DELETE FROM|ALTER TABLE|DROP TABLE IF EXISTS|CREATE TABLE)\s+([`\-_a-zA-Z]+\.[`\-_a-zA-Z]+)/i",
  240. $sql) || preg_match("/`" . $this->database . "`/",$sql)) {
  241. yield "statement" => $sql;
  242. }else{
  243. yield "statement" => preg_replace("/^(INSERT INTO|UPDATE|DELETE FROM|ALTER TABLE|CREATE TABLE|DROP TABLE IF EXISTS)\s*([`a-zA-Z_\-]*)\s*/i",
  244. "$1 " . $database . ".$2 ", $sql);
  245. }
  246. $inTable = false;
  247. $is_row = true;
  248. }
  249. }
  250. }
  251. }
  252. }
  253. /**
  254. * 根据row模式结构体生成SQL
  255. */
  256. public function analysisSql()
  257. {
  258. $this->log("正在解析binlog:".$this->file);
  259. foreach ($this->dealFile() as $mode => $value) {
  260. if (preg_match("/(INSERT INTO|UPDATE|DELETE FROM|ALTER TABLE|DROP TABLE IF EXISTS|CREATE TABLE)\s+([`\-_a-zA-Z\.]+)/i",
  261. $value, $match)) {
  262. $table = $match[2];
  263. if (preg_match("/(ALTER TABLE|DROP TABLE|CREATE TABLE)\s+([`\-_a-zA-Z\.]+)/i", $value)) {
  264. if (!preg_match("/`" . $this->database . "`/", $table)) {
  265. continue;
  266. }
  267. } else {
  268. $fields = $this->getTableFields($table);
  269. if (!$fields) {
  270. continue;
  271. }
  272. }
  273. $sql = '';
  274. if (preg_match("/^(INSERT INTO)\s+([_\-`a-zA-Z\.]+)/i", $value)) {
  275. if ($this->type && !in_array("INSERT", $this->type)) {
  276. continue;
  277. }
  278. if ($mode === "statement") {
  279. $this->out($mode, $value);
  280. continue;
  281. }
  282. //插入语句
  283. $val = "(" . implode(",", $fields) . ")";
  284. $sql = preg_replace("/^(INSERT INTO)\s+([_\-`a-zA-Z\.]+)\s+(SET)/", "$1 $2 $val", $value);
  285. if (preg_match_all("/@\d+=/", $sql, $match)) {
  286. foreach ($match[0] as $key => $v) {
  287. if ($key === 0) {
  288. $sql = str_replace($v, 'values(', $sql);
  289. } elseif ($key == count($match[0]) - 1) {
  290. $sql = preg_replace("/$v(.*)/", ",$1)", $sql);
  291. } else {
  292. $sql = str_replace($v, ',', $sql);
  293. }
  294. }
  295. }
  296. } elseif (preg_match("/^(UPDATE)\s+([_\-`a-zA-Z\.]+)/i", $value)) {
  297. if ($this->type && !in_array("UPDATE", $this->type)) {
  298. continue;
  299. }
  300. if ($mode === "statement") {
  301. $this->out($mode, $value);
  302. continue;
  303. }
  304. //更新
  305. $where = explode(" SET", $value);
  306. foreach ($where as $k => $vv) {
  307. if (preg_match_all("/\s+@\d+/", $vv, $match)) {
  308. if ($k === 0) {
  309. //SET语句
  310. foreach ($match[0] as $key => $v) {
  311. if ($key === 0) {
  312. $sql = preg_replace("/$v=/", " " . $fields[trim($v)] . "=",
  313. str_replace("WHERE", "SET", $vv));
  314. } else {
  315. $sql = preg_replace("/$v=/", "," . $fields[trim($v)] . "=", $sql);
  316. }
  317. }
  318. } else {
  319. //where 替换@ 增加 AND
  320. foreach ($match[0] as $key => $v) {
  321. if ($key === 0) {
  322. $sql .= preg_replace("/$v=/", " WHERE " . $fields[trim($v)] . "=", $vv);
  323. } else {
  324. $sql = preg_replace("/$v=/", " AND " . $fields[trim($v)] . "=", $sql);
  325. }
  326. }
  327. }
  328. }
  329. }
  330. } elseif (preg_match("/^(DELETE FROM)\s+([_\-`a-zA-Z\.]+)/i", $value)) {
  331. if ($this->type && !in_array("DELETE", $this->type)) {
  332. continue;
  333. }
  334. if ($mode === "statement") {
  335. $this->out($mode, $value);
  336. continue;
  337. }
  338. //删除
  339. if (preg_match_all("/\s+@\d+/", $value, $match)) {
  340. foreach ($match[0] as $key => $v) {
  341. if ($key === 0) {
  342. $sql = preg_replace("/$v=/", " " . $fields[trim($v)] . "=", $value);
  343. } else {
  344. $sql = preg_replace("/$v=/", " AND " . $fields[trim($v)] . "=", $sql);
  345. }
  346. }
  347. }
  348. } elseif (preg_match("/^(ALTER TABLE)\s+([_\-`a-zA-Z\.]+)/i", $value)) {
  349. //表结构更改
  350. if ($this->type && !in_array("ALTER", $this->type)) {
  351. continue;
  352. }
  353. $sql = $value;
  354. } elseif (preg_match("/^(DROP TABLE)\s+([_\-`a-zA-Z\.]+)\s*$/i", $value)) {
  355. //表结构更改
  356. if ($this->type && !in_array("DROP", $this->type)) {
  357. continue;
  358. }
  359. $sql = $value;
  360. } elseif (preg_match("/^(CREATE TABLE)\s+([_\-`a-zA-Z\.]+)/i", $value)) {
  361. //表结构更改
  362. if ($this->type && !in_array("CREATE", $this->type)) {
  363. continue;
  364. }
  365. $sql = $value;
  366. }
  367. if ($sql) {
  368. $this->out($mode, $sql);
  369. }
  370. }
  371. }
  372. }
  373. /**
  374. * 写入SQL文件
  375. */
  376. public function out($mode, $sql)
  377. {
  378. $sql = preg_replace("/\n/", ' ', $sql);
  379. if (!$this->rollback && $this->rk && preg_match("/^(INSERT INTO)\s+([_\-`a-zA-Z\.]+)/i", $sql, $match)) {
  380. //去除主键
  381. preg_match_all("/(?<=\()[^\)]+/", $sql, $field_value);
  382. if (isset($field_value[0][0]) && isset($field_value[0][1])) {
  383. $field = explode(",", $field_value[0][0]);
  384. $value = explode(",", $field_value[0][1]);
  385. if (count($field) === count($value)) {
  386. $primaryKey = $this->getPrimaryKey($match[2]);
  387. foreach ($field as $key => $v) {
  388. if ($primaryKey && preg_match("/^\s*`$primaryKey/i", $v)) {
  389. unset($field[$key]);
  390. unset($value[$key]);
  391. break;
  392. }
  393. }
  394. $sql = "insert into ".$match[2]." (".implode(",",$field).") values(".implode(",",$value).")";
  395. }
  396. }
  397. }
  398. if ($this->rollback) {
  399. //回滚SQL
  400. $sql = $this->backSql($mode, $sql);
  401. }
  402. if ($sql) {
  403. $this->sqlNums++;
  404. fwrite($this->outFile, $sql . ";" . PHP_EOL);
  405. }
  406. }
  407. /**
  408. * 生成回滚SQL
  409. * @param $mode
  410. * @param $sql
  411. * @return string
  412. */
  413. public function backSql($mode, $sql)
  414. {
  415. $roll_sql = "";
  416. if (preg_match("/^(INSERT INTO)\s+([_\-`a-zA-Z\.]+)/i", $sql, $match)) {
  417. //insert 语句=》改为delete语句
  418. preg_match_all("/(?<=\()[^\)]+/", $sql, $field_value);
  419. if (isset($field_value[0][0]) && isset($field_value[0][1])) {
  420. $field = explode(",", $field_value[0][0]);
  421. $value = explode(",", $field_value[0][1]);
  422. if (count($field) !== count($value)) {
  423. return '';
  424. }
  425. $where = "where ";
  426. $primaryKey = $this->getPrimaryKey($match[2]);
  427. foreach ($field as $key => $v) {
  428. if ($primaryKey && preg_match("/^\s*`$primaryKey/i", $v)) {
  429. $where .= $v . "=" . $value[$key];
  430. break;
  431. } else {
  432. $where .= $v . "=" . $value[$key] . " AND ";
  433. }
  434. }
  435. $where = rtrim($where, " AND ");
  436. $roll_sql = "delete from " . $match[2] . " $where";
  437. }
  438. } elseif (preg_match("/^(UPDATE)\s+([_\-`a-zA-Z\.]+)/i", $sql, $match)) {
  439. if ($mode == "row") {
  440. $set_p = stripos($sql, " set ");
  441. $where_p = strripos($sql, " where ");
  442. $set_string = preg_replace("/^\s*SET/i", " WHERE", substr($sql, $set_p, $where_p - $set_p - 1));
  443. $set_string = preg_replace("/,`/i", " AND `", $set_string);
  444. $where_string = preg_replace("/^\s*WHERE/i", " SET", substr($sql, $where_p));
  445. $where_string = preg_replace("/AND\s*`/i", ",`", $where_string);
  446. $roll_sql = $match[0] . $where_string . $set_string;
  447. }
  448. } elseif (preg_match("/^(DELETE FROM)\s+([_\-`a-zA-Z\.]+)/i", $sql, $match)) {
  449. if ($mode === "row") {
  450. $where_p = stripos($sql, " where ");
  451. $where_string = preg_replace("/^\s*WHERE/i", "", substr($sql, $where_p));
  452. $array = preg_split("/\s+AND\s+/i", $where_string);
  453. $values = "values (";
  454. $fields = "(";
  455. array_map(function ($value) use (&$values, &$fields) {
  456. $filed = substr($value, 0, stripos($value, "="));
  457. $v = substr($value, stripos($value, "=") + 1);
  458. $values .= $v . ",";
  459. $fields .= $filed . ",";
  460. }, $array);
  461. $values = trim($values, ",") . ")";
  462. $fields = trim($fields, ",") . ")";
  463. $roll_sql = "insert into " . $match[2] . " " . $fields . " " . $values;
  464. }
  465. }
  466. return $roll_sql;
  467. }
  468. /**
  469. * 获取指定表结构
  470. * @param $table
  471. * @return mixed
  472. */
  473. public function getTableFields($table)
  474. {
  475. if (!preg_match("/`" . $this->database . "`/", $table)) {
  476. return [];
  477. }
  478. $real_table = strtolower(preg_replace("/(" . $this->database . "|`|\.)/", '', $table));
  479. if ($this->searchTable && !in_array($real_table, $this->searchTable)) {
  480. //对特定表进行排查
  481. return [];
  482. }
  483. if ($this->exceptTable && in_array($real_table, $this->exceptTable)) {
  484. //对特定表进行排除
  485. return [];
  486. }
  487. if (isset($this->table[$table])) {
  488. return $this->table[$table];
  489. }
  490. if ($result = $this->mysqli->query("show FULL FIELDS from $table")) {
  491. $result = $result->fetch_all();
  492. } else {
  493. return [];
  494. }
  495. foreach ($result as $k => $value) {
  496. $this->table[$table]['@' . ($k + 1)] = "`" . $value[0] . "`";
  497. }
  498. return $this->table[$table];
  499. }
  500. /**
  501. * 获取表主键
  502. * @param $table
  503. * @return string
  504. */
  505. public function getPrimaryKey($table)
  506. {
  507. if (!preg_match("/`" . $this->database . "`/", $table)) {
  508. return '';
  509. }
  510. $real_table = strtolower(preg_replace("/(" . $this->database . "|`|\.)/", '', $table));
  511. if ($this->searchTable && !in_array($real_table, $this->searchTable)) {
  512. //对特定表进行排查
  513. return '';
  514. }
  515. if ($this->exceptTable && in_array($real_table, $this->exceptTable)) {
  516. //对特定表进行排除
  517. return '';
  518. }
  519. if (isset($this->primaryKey[$table])) {
  520. return $this->primaryKey[$table];
  521. }
  522. if ($result = $this->mysqli->query("show keys from $table where key_name='PRIMARY'")) {
  523. $result = $result->fetch_all();
  524. if (!empty($result)) {
  525. $this->primaryKey[$table] = $result[0][4];
  526. }
  527. } else {
  528. return "";
  529. }
  530. return $this->primaryKey[$table];
  531. }
  532. /**
  533. * 读取文件
  534. * @param int $key
  535. * @return Generator
  536. */
  537. public function readFile($key = 1)
  538. {
  539. $handle = fopen($this->file, 'r');
  540. while (feof($handle) === false) {
  541. $this->progress($key);
  542. $key++;
  543. yield fgets($handle);
  544. }
  545. fclose($handle);
  546. }
  547. /**
  548. * 获取总行数
  549. */
  550. public function getAllLines()
  551. {
  552. $this->startTime = time();
  553. $handle = fopen($this->file, 'r');
  554. $line = 0;
  555. $this->log("开始统计行数");
  556. while (feof($handle) === false) {
  557. fgets($handle);
  558. $line++;
  559. }
  560. fclose($handle);
  561. $this->lines = $line;
  562. $this->log("统计完成,总行数:$line");
  563. }
  564. /**
  565. * 日志
  566. * @param $info
  567. * @param bool $die
  568. */
  569. public function log($info, $die = false)
  570. {
  571. if ($die) {
  572. echo "\033[0;31m\n提示:".$info."\033[0m".PHP_EOL;
  573. exit("程序终止" . PHP_EOL);
  574. }else{
  575. echo "\033[0;32m$info\033[0m".PHP_EOL;
  576. }
  577. }
  578. //进度条
  579. public function progress($key)
  580. {
  581. $kb = memory_get_usage() / 1024;
  582. $kb = round(($kb > 1024 ? $kb / 1024 : $kb), 2) . ($kb > 1024 ? "MB" : "KB");
  583. $max_kb = memory_get_peak_usage() / 1024;
  584. $max_kb = round(($max_kb > 1024 ? $max_kb / 1024 : $max_kb), 2) . ($max_kb > 1024 ? "MB" : "KB");
  585. $time = time() - $this->startTime;
  586. $time = $time > 60 ? (floor($time / 60) . "min" . ($time - floor($time / 60) * 60) . "s") : $time . "s";
  587. printf("\033[1;33mprogress: [%-50s] %d%% Done;time:%s;sql nums:%d;Memory usage:%s;Memory max usage:%s\r\033[0m",
  588. str_repeat('>', $key / ($this->lines) * 50), $key / ($this->lines) * 100, $time, $this->sqlNums, $kb,
  589. $max_kb);
  590. }
  591. /**
  592. * 销毁资源
  593. */
  594. public function __destruct()
  595. {
  596. // TODO: Implement __destruct() method.
  597. if ($this->outFile) {
  598. fclose($this->outFile);
  599. }
  600. if ($this->mysqli) {
  601. $this->mysqli->close();
  602. }
  603. $this->log("\e[0;31;1m执行结束");
  604. }
  605. }
  606. $mysql = new mysql_analysis();
  607. $mysql->analysisSql();
  608. unset($mysql);
  1. 文件格式(该脚本适用于分析此类格式文件)
    1. row模式
    2. #191121 11:21:22 server id 460911738 end_log_pos 6569752 CRC32 0x578dff18 Table_map: `bee`.`jobs` mapped to number 109
    3. # at 6569752
    4. #191121 11:21:22 server id 460911738 end_log_pos 6570489 CRC32 0xe2c32a78 Write_rows: table id 109 flags: STMT_END_F
    5. ### INSERT INTO `bee`.`jobs`
    6. ### SET
    7. ### @1=515599 /* LONGINT meta=0 nullable=0 is_null=0 */
    8. ### @2='default' /* VARSTRING(1020) meta=1020 nullable=0 is_null=0 */
    9. ### @3='{"displayName":"App\x5c\x5cJobs\x5c\x5cOperationRecord","job":"Illuminate\x5c\x5cQueue\x5c\x5cCallQueuedHandler@call","maxTries":5,"timeout":null,"timeoutAt":null,"data":{"commandName":"App\x5c\x5cJobs\x5c\x5cOperationRecord","command":"O:24:\x5c"App\x5c\x5cJobs\x5c\x5cOperationRecord\x5c":13:{s:6:\x5c"\x5cu0000*\x5cu0000uri\x5c";s:11:\x5c"\x5c/api\x5c/notify\x5c";s:5:\x5c"\x5cu0000*\x5cu0000as\x5c";s:12:\x5c"normalNotify\x5c";s:7:\x5c"\x5cu0000*\x5cu0000uses\x5c";s:50:\x5c"App\x5c\x5cHttp\x5c\x5cControllers\x5c\x5cNotifyController@normalNotify\x5c";s:9:\x5c"\x5cu0000*\x5cu0000params\x5c";s:2:\x5c"[]\x5c";s:10:\x5c"\x5cu0000*\x5cu0000user_id\x5c";i:995;s:5:\x5c"tries\x5c";i:5;s:6:\x5c"\x5cu0000*\x5cu0000job\x5c";N;s:10:\x5c"connection\x5c";N;s:5:\x5c"queue\x5c";N;s:15:\x5c"chainConnection\x5c";N;s:10:\x5c"chainQueue\x5c";N;s:5:\x5c"delay\x5c";N;s:7:\x5c"chained\x5c";a:0:{}}"}}' /* LONGBLOB/LONGTEXT meta=4 nullable=0 is_null=0 */
    10. ### @4=0 /* TINYINT meta=0 nullable=0 is_null=0 */
    11. ### @5=NULL /* INT meta=0 nullable=1 is_null=1 */
    12. ### @6=1574306482 /* INT meta=0 nullable=0 is_null=0 */
    13. ### @7=1574306482 /* INT meta=0 nullable=0 is_null=0 */
    14. # at 6570489
    15. #191121 11:21:22 server id 460911738 end_log_pos 6570520 CRC32 0xcec70d2a Xid = 38772244
    16. COMMIT/*!*/;
    17. # at 6570520
    statement模式
    # at 37849
    #191122 14:43:10 server id 1  end_log_pos 37849 CRC32 0x3ade880b     Intvar
    SET INSERT_ID=483677/*!*/;
    #191122 14:43:10 server id 1  end_log_pos 38598 CRC32 0x9f3d299e     Query    thread_id=2    exec_time=0    error_code=0
    SET TIMESTAMP=1574404990/*!*/;
    insert into `operation_record` (`uri`, `as`, `uses`, `data`, `comment`, `user_id`, `updated_at`, `created_at`) values ('/api/auth/login', 'login', 'App\\Http\\Controllers\\AuthController@index', '{\"code\":\"855353\",\"phone\":\"13708082028\",\"latitude\":\"30.662541\",\"location\":\"\\u56db\\u5ddd\\u7701\\u6210\\u90fd\\u5e02\\u6210\\u534e\\u533a\\u6210\\u534e\\u5927\\u9053\\u65b0\\u9e3f\\u8def84\\u53f7\\u9760\\u8fd1\\u56db\\u5ddd\\u5929\\u5e9c\\u94f6\\u884c(\\u6210\\u90fd\\u6210\\u534e\\u652f\\u884c)\",\"model\":\"OPPO PACM00\",\"version\":\"Android 9\",\"longitude\":\"104.095424\"}', '接口登录校验', 0, '2019-11-22 14:43:10', '2019-11-22 14:43:10')
    /*!*/;
    # at 38598
    #191122 14:43:10 server id 1  end_log_pos 38629 CRC32 0x4081ef04     Xid = 797
    COMMIT/*!*/;
    # at 38629
    #191122 14:43:10 server id 1  end_log_pos 38694 CRC32 0x7237e64e     Anonymous_GTID    last_committed=89    sequence_number=90    rbr_only=no
    SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
    # at 38694
    #191122 14:43:10 server id 1  end_log_pos 38785 CRC32 0x3b89e911     Query    thread_id=2    exec_time=0    error_code=0
    SET TIMESTAMP=1574404990/*!*/;
    BEGIN
    /*!*/;
    # at 38785
    #191122 14:43:10 server id 1  end_log_pos 38909 CRC32 0x52ca6fab     Query    thread_id=2    exec_time=0    error_code=0
    SET TIMESTAMP=1574404990/*!*/;
    delete from `jobs` where `id` = 515606
    /*!*/;
    # at 38909
    #191122 14:43:10 server id 1  end_log_pos 38940 CRC32 0xaf4f0661     Xid = 801
    COMMIT/*!*/;
    # at 38940
    
执行方式:php mysql_analysis.php -h10.0.108.58 -ubee -dbee -pzxzdapp666 -mbinlog.txt --output=all.sql --table=wallet/user_role/jobs --except=wallet/user_role --port=3306