thumbtack/querycache

一个简单的PDO查询接口,支持结果集和行级缓存

0.0.5 2023-01-20 06:09 UTC

This package is not auto-updated.

Last update: 2024-09-14 18:52:59 UTC


README

围绕PDO构建的简单查询缓存层。它可以缓存到MemcachedAPCu、本地请求缓存,或任何实现了CacheInterface的缓存。

Composer 安装

"require": {
    "thumbtack/querycache": "^0.1"
}

基本查询

基本的 Query 接口通过 readwrite 方法暴露,这些方法是对常见的PDO数据访问模式的包装。 read 用于任何将返回结果集的操作。 write 用于任何不返回结果集但可能更改数据的操作。

// initialize PDO object that Query will run on top of:
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$pdo = new \PDO($dsn);
$query = new \QueryCache\Query($pdo);

$sql = 'SELECT id, name, email FROM users WHERE id = :id';
$params = [ ':id' => 1];
$results = $query->read($sql, $params);

//example response:
// $results = [
//   ['id' => 1, 'name' => 'Joe', 'email' => 'joe@example.com'],
// ];

$sql = 'SELECT id, name, email FROM users WHERE id IN (:id)';
$params = [ ':id' => [1, 2, 3] ];
$results = $query->read($sql, $params);

//example response:
// $results = [
//   ['id' => 1, 'name' => 'Joe', 'email' => 'joe@example.com'],
//   ['id' => 2, 'name' => 'Kim', 'email' => 'kim@example.com'],
//   ['id' => 3, 'name' => 'Bob', 'email' => 'bob@example.com'],
// ];

上面的示例将创建一个预定义语句,该语句仅使用命名参数。绑定参数将通过传递。由于在第二个查询示例中 ':id' 是一个数组,因此它将扩展为绑定参数的列表。

上述示例中没有使用缓存。

缓存查询读取

缓存查询与非缓存查询的工作方式相同,但您需要指定一个 result_set_cache 键/键模板和一个/或 row_cache 键模板,并将其作为第三个参数传递给 readwrite。键模板是一个包含 :tokens 的字符串。 :tokens 应与传递参数的至少一个子集相同。这些令牌将被替换为传递给查询的参数的各个值。以下是一个相同的示例,除了使用行级缓存外

// initialize PDO object that Query will run on top of:
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$pdo = new \PDO($dsn);
$cache = new \QueryCache\LocalCache();
$query = new \QueryCache\Query($pdo, $cache);

$sql = 'SELECT id, name, email FROM users WHERE id = :id';
$params = [ ':id' => 1];
$options = [ 'row_cache' => '/users/:id' ];
$results = $query->read($sql, $params, $options);

//example response:
// $results = [
//   ['id' => 1, 'name' => 'Joe', 'email' => 'joe@example.com'],
// ];

$sql = 'SELECT id, name, email FROM users WHERE id IN (:id)';
$params = [ ':id' => [1, 2, 3] ];
$options = [ 'row_cache' => '/users/:id' ];
$results = $query->read($sql, $params);

//example response:
// $results = [
//   ['id' => 1, 'name' => 'Joe', 'email' => 'joe@example.com'],
//   ['id' => 2, 'name' => 'Kim', 'email' => 'kim@example.com'],
//   ['id' => 3, 'name' => 'Bob', 'email' => 'bob@example.com'],
// ];

上面的示例定义了一个 row_cache 模板字符串 /users/:id。这允许我们检查传递的参数,构建所有可能的缓存键,并检查我们是否可以从缓存中满足查询。如果我们可以,则返回缓存的查询结果。如果我们不能,我们将回退并执行查询逻辑。如果得到部分匹配,传递给最终查询的参数将被修改,以仅包括不在缓存中的项的参数。如果我们必须运行查询,我们还将缓存返回的行,以匹配其对应的键,以便后续调用可以命中缓存。

row_cache 将缓存结果集中返回的每个单独的行,每个键应评估为一个对于该行唯一的字符串。一个 result_set_cache 缓存整个结果集,并且在缓存中发现任何内容时都不会回退到查询数据库。

在带有缓存的查询中存在一些其他选项。

  • ttl - 这将为在调用过程中缓存的任何值设置以秒为单位的 ttl。
  • map - 这将将扁平数组结果集转换为映射/关联数组。
  • sort - 这基本上是一个 SQL ORDER BY 语句。当我们使用行级缓存时,我们不能保证从缓存中返回行的顺序。这允许我们强制执行一个顺序。

使用这些参数的示例

// initialize PDO object that Query will run on top of:
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$pdo = new \PDO($dsn);
$cache = new \QueryCache\LocalCache();
$query = new \QueryCache\Query($pdo, $cache);

$sql = 'SELECT id, name, email FROM users WHERE id IN (:id)';
$params = [ ':id' => [1, 2, 3] ];
$options = [
    'row_cache' => '/users/:id',
    'ttl' => 300,
    'map' => ['id', 'name'],
    'sort' => 'id DESC'
];
$results = $query->read($sql, $params);

//example response:
// $results = [
//   3 => [ 'Bob' => ['id' => 3, 'name' => 'Bob', 'email' => 'bob@example.com'] ],
//   2 => [ 'Kim' => ['id' => 2, 'name' => 'Kim', 'email' => 'kim@example.com'] ],
//   1 => [ 'Joe' => ['id' => 1, 'name' => 'Joe', 'email' => 'joe@example.com'] ],
// ];

在上面的示例中,缓存的 ttl 为 300 秒(5 分钟)。我们按 id DESC 对返回的结果进行排序,并从 idname 构建 map

您还可以堆叠缓存,从而拥有多层缓存。

// initialize PDO object that Query will run on top of:
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$pdo = new \PDO($dsn);

$servers = [
    [ 'host' => '127.0.0.1', 'port' => 11211, 'weight' => 1, ]
];
$local = new \QueryCache\LocalCache();
$memcache = new \QueryCache\Memcache(['servers' => $servers]);
$stack = new \QueryCache\CacheStack([$local, $memcache]);

$query = new \QueryCache\Query($pdo, $stack);

这将首先从 LocalCache 中读取,如果未命中,则会查看 Memcache。然后,在写入时,它将写入 Memcache,并随后立即写入 LocalCache

还有一个 CacheLog,它将记录特定缓存的缓存活动。

// initialize PDO object that Query will run on top of:
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$pdo = new \PDO($dsn);

$servers = [
    [
        'host' => '127.0.0.1',
        'port' => 11211,
        'weight' => 1,
    ]
];
$local = new \QueryCache\LocalCache();
$memcache = new \QueryCache\Memcache(['servers' => $servers]);
$logger = new Psr3Logger(); // not defined in this project
$logged_memcache = new \QueryCache\CacheLog($memcache, $logger);
$stack = new \QueryCache\CacheStack([$local, $logged_memcache]);

$query = new \QueryCache\Query($pdo, $stack);

这将记录对 Memcache 对象的单独方法调用。如果您没有将 psr-3 兼容的记录器传递给 CacheLog,它仍然会跟踪所有各种缓存活动,并且可以通过调用 CacheLog::GetActivityBuffer() 获取。这将返回针对缓存的 调用 数量,缓存的总体 运行时间,然后是所有单独调用的 活动。每个 活动 条目包括调用 运行时间、调用的 、调用的 方法、方法交互的 ,以及调用中的键有多少是 命中未命中(适用于适当的情况)。

缓存查询写入

写入数据的接口几乎与读取数据相同,只是选项较少。您只能定义 row_cache 字符串/模板和/或 result_set_cache 字符串/模板。在写入时,我们生成的任何缓存键都将从缓存中驱逐。

示例写入(基于上一个示例)

// initialize PDO object that Query will run on top of:
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$pdo = new \PDO($dsn);

$servers = [
    [
        'host' => '127.0.0.1',
        'port' => 11211,
        'weight' => 1,
    ]
];
$local = new \QueryCache\LocalCache();
$memcache = new \QueryCache\Memcache(['servers' => $servers]);
$logger = new Psr3Logger(); // not defined in this project
$logged_memcache = new \QueryCache\CacheLog($memcache, $logger);
$stack = new \QueryCache\CacheStack([$local, $logged_memcache]);

$query = new \QueryCache\Query($pdo, $stack);

$sql = 'UPDATE users SET name = :name, email = :email WHERE id = :id';
$params = [ ':name' => 'Sam', ':email' => 'sam@example.com', ':id' => 1 ];
$options = [ 'row_cache' => '/users/:id' ];
$query->write($sql, $params, $options);

上面的代码将更新 users 表,并在更新完成后驱逐 /users/1 缓存键。这样,下次查询它时,就会拉取更新的值。

待办事项

  • 使缓存层与 PSR-6 兼容或更新到 PSR-6 库。
  • 添加抖动、锁定和通过驱逐进行再生的选项。
  • 记录附加选项。
  • 将应跨所有 CacheInterface 实现者通用的事项移动到 CacheInterface。
  • 构建可选的 QueryOptions 对象,以便我们可以有更好的选项类型检查。
  • 可能支持不同的 fetch 模式,这使得 mapsort 选项更难以处理。