SQL 镜像
SqlMirror 会将 storh 集合推送到 SQLite 或 MySQL 表中,以便你在 SQL 中执行联接、跨表排序、子字符串搜索和报告,同时文件仍是权威数据源。可以将其视为一个恰好采用数据库形式的二级索引:镜像是派生数据,可随时丢弃并重建。仅在使用时,才需要通过 sqlite 或 mysql 驱动程序连接 ext-pdo;核心软件包不依赖任何其他软件包。
设置
use Storh\DocStore;
use Storh\Schema;
use Storh\SqlMirror;
$schema = Schema::collection('pages')
->string('slug')->unique()
->string('kind')->index()
->int('publishedAt')->range();
$pages = new DocStore($root, 'pages', schema: $schema);
$events = new Storh\SegmentedLog($root, 'events');
$mirror = new SqlMirror(new PDO('sqlite:' . $root . '/mirror.db'), 'app_');
$mirror->collection($pages, 'pages', $schema);
$mirror->collection($events, 'events');
$mirror->install();
$mirror->push();
两种引擎都可以创建镜像:DocStore 集合和 SegmentedLog 流(只推送活动记录)。传入 Schema 是可选的,且独立于存储:声明为 string/int/float/bool 的字段会成为类型化索引列;其他所有内容都可通过 data JSON 列进行查询。mixed 字段不能设置索引标志。
连接
构造函数接受 PDO 句柄(sqlite 或 mysql 驱动程序,会自动检测)、mysqli 句柄,或任何 SqlMirrorConnection 实现:
$mirror = new SqlMirror(new PDO('sqlite:' . $root . '/mirror.db'));
$mirror = new SqlMirror(new PDO('mysql:host=127.0.0.1;dbname=app;charset=utf8mb4', $user, $password));
$mirror = new SqlMirror(new mysqli('127.0.0.1', $user, $password, 'app'), 'wp_storh_');
mysqli 路径适用于任何一种 mysqli 错误报告模式,因此可直接重复使用主机应用程序中现有的句柄(例如 WordPress 的 $wpdb->dbh)。各驱动程序的行为一致;所有驱动程序在推送失败时都会回滚集合的事务。
每张表都有 id(主键)、hash(用于增量推送)、类型化列,以及 data(完整记录的 JSON)。
同步
$mirror->push(); // reconcile: insert, update, delete as needed
$mirror->push('pages'); // reconcile a single collection
$mirror->flush('pages', $ids); // push or remove just these ids
$mirror->pull(); // write mirror rows back into the store
$mirror->verify(); // report missing, stale, and orphaned rows
$mirror->rebuild(); // clear all rows and push from scratch
$mirror->uninstall(); // drop the mirror tables
push() 会将记录哈希与镜像进行比较,并在一个事务中写入每个集合,因此推送过程中发生崩溃或失败都不会留下只应用了一部分的批次。未更改的记录只需一次 SELECT 和一次记录扫描。定期执行 push()(例如在 cron 时触发)可使镜像保持同步;如果读者需要从镜像读取请求内刚写入的记录,请使用所写记录的 id 调用 flush()。
查询
使用纯 SQL 查询镜像。table() 会返回镜像表名:
$pdo->query(
'SELECT p.slug, COUNT(*) AS sales
FROM ' . $mirror->table('purchases') . ' o
INNER JOIN ' . $mirror->table('products') . ' p ON p.id = o.product_id
GROUP BY p.slug
ORDER BY sales DESC'
);
拉取回文件
pull() 会读取镜像行,并按 id 顺序将其写入已注册的存储。它支持两种流程:
- 恢复:文件已丢失,但镜像仍然存在。在同一个集合名称下注册一个新的存储,然后执行拉取。
- 初始化数据:通过外部工具载入镜像表的行会变成 storh 记录。行必须包含有效的 UUIDv7 id,并在
data列中包含对象;拉取时会忽略类型化列,因为data就是完整记录。
$restored = new Storh\DocStore($root, 'pages');
$mirror = new SqlMirror($pdo);
$mirror->collection($restored, 'pages');
$mirror->pull(); // ['written' => n, 'unchanged' => m]
拉取写入会经过正常的存储处理流程,因此会应用架构验证和唯一索引,并且每条记录都会持久写入。pull() 不会删除本地记录;如果记录的数据已与镜像行一致,则会跳过。为手动插入的行初始化数据后,请运行一次 push():它会以规范形式重写这些行,使其哈希值保持一致。
约定和限制
- 文件是权威数据源。镜像是只读输出;手动写入其中的任何内容都会被下一次
push()覆盖或删除。 - 镜像中的唯一列是后备保护机制,不会在写入时强制执行:如果文件接受了重复值,就会导致推送失败并完整回滚。要在写入时强制执行,请使用 storh 唯一索引。
- 镜像最多会比文件落后一个推送间隔,但你明确
flush()的 id 除外。 - 创建镜像不会为 storh 本身增加事务。跨多条记录的原子写入仍应交由作为权威数据源的真正数据库处理。
- 架构变更通过删除并重新安装镜像来应用:
uninstall()、install()、push()。