本文目录导读:

在 PHP 中使用列存(Columnar Storage)主要有以下几种方式,我会从简单到复杂介绍:
使用列存数据库
ClickHouse(推荐)
<?php
// 使用 ClickHouse 的 PHP 客户端
require_once 'vendor/autoload.php';
use ClickHouseDB\Client;
$config = [
'host' => 'localhost',
'port' => '8123',
'username' => 'default',
'password' => ''
];
$client = new Client($config);
// 创建列存表
$client->write('
CREATE TABLE IF NOT EXISTS analytics.events (
event_id UInt64,
event_time DateTime,
event_type String,
user_id UInt64,
data String
) ENGINE = MergeTree()
ORDER BY (event_time)
');
// 批量插入数据
$client->insert(
'events',
[
[1, '2024-01-01 10:00:00', 'click', 100, '{"page": "/home"}'],
[2, '2024-01-01 10:01:00', 'view', 101, '{"page": "/about"}'],
// ... 更多数据
],
['event_id', 'event_time', 'event_type', 'user_id', 'data']
);
// 列存高效的聚合查询
$result = $client->select('
SELECT
event_type,
COUNT(*) as cnt,
AVG(user_id) as avg_user
FROM events
WHERE event_time >= today()
GROUP BY event_type
');
print_r($result->rows());
Apache Cassandra
<?php
// 使用 DataStax PHP Driver
require_once 'vendor/autoload.php';
use Cassandra\Cluster;
use Cassandra\SimpleStatement;
$cluster = Cluster::builder()
->withContactPoints('127.0.0.1')
->build();
$session = $cluster->connect('analytics');
// 创建列族(表)
$session->execute(new SimpleStatement('
CREATE TABLE IF NOT EXISTS user_events (
user_id uuid,
event_time timestamp,
event_type text,
PRIMARY KEY (user_id, event_time)
) WITH CLUSTERING ORDER BY (event_time DESC)
'));
// 插入数据
$prepared = $session->prepare(
'INSERT INTO user_events (user_id, event_time, event_type) VALUES (?, ?, ?)'
);
$session->execute($prepared, [
'arguments' => [
new Cassandra\Uuid('12345678-1234-1234-1234-123456789012'),
new Cassandra\Timestamp(time()),
'click'
]
]);
// 查询
$result = $session->execute(new SimpleStatement('
SELECT event_type, COUNT(*) as cnt
FROM user_events
WHERE user_id = ?
GROUP BY event_type
'), ['arguments' => [new Cassandra\Uuid('12345678-1234-1234-1234-123456789012')]]);
foreach ($result as $row) {
echo "Event: {$row['event_type']}, Count: {$row['cnt']}\n";
}
使用 PHP 扩展
Apache Arrow(内存列存格式)
<?php
// 需要安装 ext-arrow
$schema = new \Arrow\Schema([
'id' => \Arrow\Type::INT64,
'name' => \Arrow\Type::STRING,
'score' => \Arrow\Type::FLOAT64
]);
$array = new \Arrow\RecordBatch($schema);
// 按列添加数据(列优先存储)
$array->addColumn('id', [1, 2, 3, 4, 5]);
$array->addColumn('name', ['Alice', 'Bob', 'Charlie', 'David', 'Eve']);
$array->addColumn('score', [85.5, 92.3, 78.9, 88.6, 95.1]);
// 列存的高效列操作
$scores = $array->column('score');
$average = array_sum($scores) / count($scores);
echo "Average score: $average\n";
自己实现简单的列存
<?php
class SimpleColumnStore {
private $columns = [];
private $rowCount = 0;
// 添加列数据
public function addColumn($name, array $data) {
if ($this->rowCount === 0) {
$this->rowCount = count($data);
}
if (count($data) !== $this->rowCount) {
throw new Exception("Column length mismatch");
}
$this->columns[$name] = $data;
}
// 获取指定列(列存最优操作)
public function getColumn($name) {
return $this->columns[$name] ?? null;
}
// 聚合操作(列存高效场景)
public function aggregate($column, $operation) {
$data = $this->getColumn($column);
if (!$data) return null;
switch ($operation) {
case 'sum':
return array_sum($data);
case 'avg':
return array_sum($data) / count($data);
case 'min':
return min($data);
case 'max':
return max($data);
default:
return null;
}
}
// 条件筛选(列存劣势场景)
public function filter($column, $callback) {
$indices = array_keys(array_filter($this->columns[$column], $callback));
$result = new self();
foreach ($this->columns as $name => $data) {
$result->addColumn($name, array_intersect_key($data, array_flip($indices)));
}
return $result;
}
}
// 使用示例
$store = new SimpleColumnStore();
$store->addColumn('id', range(1, 1000000));
$store->addColumn('value', range(100, 1000000 + 99));
// 高效的聚合操作
echo $store->aggregate('value', 'avg'); // 500000.5
使用 row-oriented 转 column-oriented 的工具
<?php
// 将行式数据转换为列式存储
class RowToColumnTransformer {
public static function transform(array $rows) {
$columns = [];
foreach ($rows as $row) {
foreach ($row as $key => $value) {
$columns[$key][] = $value;
}
}
return $columns;
}
}
// 使用
$data = [
['id' => 1, 'name' => 'Alice', 'age' => 25],
['id' => 2, 'name' => 'Bob', 'age' => 30],
['id' => 3, 'name' => 'Charlie', 'age' => 35],
];
$columnar = RowToColumnTransformer::transform($data);
print_r($columnar);
// 输出:['id' => [1,2,3], 'name' => ['Alice','Bob','Charlie'], 'age' => [25,30,35]]
现代数据库的列存支持
<?php
// MySQL 8.0+ 不原生支持,但可以通过临时表模拟
// PostgreSQL 12+ 的列存扩展
$pdo = new PDO('pgsql:host=localhost;dbname=test', 'user', 'pass');
// 创建列存表(使用 cstore_fdw 扩展)
$pdo->exec("CREATE EXTENSION IF NOT EXISTS cstore_fdw");
$pdo->exec("CREATE SERVER cstore_server FOREIGN DATA WRAPPER cstore_fdw");
$pdo->exec("CREATE FOREIGN TABLE events (
id integer,
timestamp timestamp,
event_type text
) SERVER cstore_server OPTIONS(compression 'pglz')");
// 插入数据(会自动转换为列存)
$stmt = $pdo->prepare("INSERT INTO events VALUES (?, ?, ?)");
$stmt->execute([1, time(), 'click']);
// 列存的聚合查询
$result = $pdo->query("
SELECT event_type, COUNT(*)
FROM events
GROUP BY event_type
");
推荐方案
- 生产环境:使用 ClickHouse 或 Doris(列存数据库)
- 中等复杂度:使用 Apache Arrow 或 Parquet 格式
- 学习/测试:自己实现简单列存结构
- 已有数据库:使用 PostgreSQL 的列存扩展
列存最适合的场景:
- 大规模数据分析
- 聚合查询
- 只读场景
- 压缩存储
不适合:
- 高频更新
- 点查询(获取单行多列)
- OLTP 场景
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播