mysqlbinlog 工具需順序讀取全文件才能取得結束時間,大檔案耗時極長。MySQL Binlog 格式公開,可直接解析文件頭與尾部事件快速獲取起止時間。
0xFE 0x62 0x69 0x6E(fe bin)。<?php
/**
* 讀取 Binlog 起止時間、下一檔名、Server 版本
*/
class BinlogInfo {
const EVENT_HEAD_SIZE = 19;
const FORMAT_DESCRIPTION_EVENT_DATA_SIZE = 59;
const BINLOG_HEAD = "\xfebin";
const FORMAT_DESCRIPTION_EVENT = 15;
const ROTATE_EVENT = 4;
private $eventHeadPackStr = '';
private $formatDescriptionEventDataPackStr = '';
public function __construct() {
$this->eventHeadPackStr = $this->buildPackStr([
'timestamp' => 'l',
'type_code' => 'c',
'server_id' => 'l',
'event_length' => 'l',
'next_position' => 'l',
'flags' => 's',
]);
$this->formatDescriptionEventDataPackStr = $this->buildPackStr([
'binlog_version' => 's',
'server_version' => 'a50',
'create_timestamp' => 'l',
'head_length' => 'c',
]);
}
private function buildPackStr(array $struct): string {
return implode('/', array_map(fn($v, $k) => $v . $k, $struct, array_keys($struct)));
}
protected function isBinlog($file): bool {
rewind($file);
return fread($file, strlen(self::BINLOG_HEAD)) === self::BINLOG_HEAD;
}
protected function readFormatDescriptionEvent($file): ?array {
fseek($file, strlen(self::BINLOG_HEAD), SEEK_SET);
$head = unpack($this->eventHeadPackStr, fread($file, self::EVENT_HEAD_SIZE));
if ($head['type_code'] !== self::FORMAT_DESCRIPTION_EVENT) return null;
$data = unpack($this->formatDescriptionEventDataPackStr, fread($file, self::FORMAT_DESCRIPTION_EVENT_DATA_SIZE));
return ['head' => $head, 'data' => $data];
}
protected function readRotateEvent($file): ?array {
$bufsize = 100;
$size_pos = 8; // 64bit position
fseek($file, -$bufsize, SEEK_END);
$buf = fread($file, $bufsize);
$min_begin = strlen(self::BINLOG_HEAD) + self::EVENT_HEAD_SIZE + $size_pos;
$ok = false;
for ($i = $bufsize - 1; $i > $min_begin; $i--) {
if ($buf[$i] === "\0") { $ok = true; break; }
}
if (!$ok) return null;
$next_filename = substr($buf, $i + 1);
$head_str = substr($buf, $i + 1 - $size_pos - self::EVENT_HEAD_SIZE, self::EVENT_HEAD_SIZE);
$head = unpack($this->eventHeadPackStr, $head_str);
if ($head['type_code'] !== self::ROTATE_EVENT) return null;
return ['head' => $head, 'nextFile' => $next_filename];
}
public function read(string $path): ?array {
$file = fopen($path, 'r');
if (!$file || !$this->isBinlog($file)) { if ($file) fclose($file); return null; }
$fde = $this->readFormatDescriptionEvent($file);
$re = $this->readRotateEvent($file);
fclose($file);
if (!$fde || !$re) return null;
return [
'beginAt' => $fde['head']['timestamp'],
'endAt' => $re['head']['timestamp'],
'nextFile' => $re['nextFile'],
'serverVersion'=> rtrim($fde['data']['server_version'], "\0"),
];
}
}
// 使用範例
$info = (new BinlogInfo())->read('/var/lib/mysql/mysql-bin.000001');
print_r($info);
Array
(
[beginAt] => 1704067200
[endAt] => 1704153600
[nextFile] => mysql-bin.000002
[serverVersion] => 5.7.42-log
)
mysqlbinlog,無需順序掃描全文件。