since / clickhouse
一个类 ThinkPHP 风格的 ClickHouse 链式查询组件
1.0.0
2025-09-23 08:37 UTC
Requires
- php: >=8.0
- ext-curl: *
- ext-json: *
Requires (Dev)
- mockery/mockery: ^1.4
- phpunit/phpunit: ^9.0
README
一个类 ThinkPHP 风格的 ClickHouse 链式查询组件,让 PHP 开发者能像操作 MySQL 一样轻松操作 ClickHouse。
特性
- ✅ 类 ThinkPHP 链式查询语法
- ✅ 支持 ClickHouse 原生 SQL 与 Builder 混合使用
- ✅ 支持 HTTP 协议(默认)与 TCP 协议(可选)
- ✅ 支持 WHERE、ORDER BY、GROUP BY、LIMIT、HAVING、SELECT、FIELD、JOIN(ASOF / INNER / LEFT)等常用语法
- ✅ 支持数据插入(INSERT)、批量插入、替代插入(ReplacingMergeTree)
- ✅ 支持预处理防注入(参数绑定)
- ✅ 支持 Laravel、ThinkPHP、Lumen 等主流框架集成
- ✅ 提供假事务机制(用于API兼容性)
- ✅ 提供详细文档与使用示例
安装
通过 Composer 安装:
composer require since/clickhouse
配置
基本配置
use ClickHouse\DB; // 设置配置信息 DB::setConfig([ 'default' => 'default', 'connections' => [ 'default' => [ 'protocol' => 'http', 'host' => 'localhost', 'port' => 8123, 'database' => 'default', 'username' => 'default', 'password' => '', 'timeout' => 30, 'debug' => false, ], ], ]);
Laravel 集成
在 Laravel 中,发布配置文件:
php artisan vendor:publish --tag=clickhouse-config
然后在 .env
文件中配置:
CLICKHOUSE_HOST=localhost
CLICKHOUSE_PORT=8123
CLICKHOUSE_DATABASE=default
CLICKHOUSE_USERNAME=default
CLICKHOUSE_PASSWORD=
ThinkPHP 集成
在 ThinkPHP 中,创建 config/clickhouse.php
配置文件:
return [ 'default' => 'default', 'connections' => [ 'default' => [ 'protocol' => 'http', 'host' => '127.0.0.1', 'port' => 8123, 'database' => 'default', 'username' => 'default', 'password' => '', 'timeout' => 30, 'debug' => false, ], ], ];
然后在服务提供者中注册:
use ClickHouse\DB; // 在应用启动时加载配置 DB::setConfig(config('clickhouse'));
基本用法
查询数据
use ClickHouse\DB; // 基本查询 $data = DB::name('user_log') ->where('uid', '>', 100) ->where('date', '>=', '2025-09-01') ->whereIn('type', [1, 2, 3]) ->field('uid, sum(score) as total') ->group('uid') ->having('total', '>', 100) ->order('total desc') ->limit(10) ->select(); // 获取单条记录 $user = DB::name('users') ->where('id', 1) ->first(); // 获取单个值 $name = DB::name('users') ->where('id', 1) ->value('name'); // 聚合查询 $count = DB::name('user_log')->count(); $max = DB::name('user_log')->max('score'); $min = DB::name('user_log')->min('score'); $avg = DB::name('user_log')->avg('score'); $sum = DB::name('user_log')->sum('score');
条件查询
// 基本条件 DB::name('user_log')->where('uid', 123); DB::name('user_log')->where('age', '>', 18); // 组合条件 DB::name('user_log') ->where('uid', 123) ->where('age', '>', 18) ->where('status', 'in', [1, 2]); // OR 条件 DB::name('user_log') ->where('uid', 123) ->orWhere('uid', 456); // 原生条件 DB::name('user_log') ->whereRaw("toDate(time) = '2025-09-23'"); // IN 条件 DB::name('user_log') ->whereIn('uid', [1, 2, 3]); // BETWEEN 条件 DB::name('user_log') ->whereBetween('age', 18, 30); // NULL 条件 DB::name('user_log') ->whereNull('deleted_at'); // NOT IN 条件 DB::name('user_log') ->whereNotIn('uid', [1, 2, 3]); // NOT BETWEEN 条件 DB::name('user_log') ->whereNotBetween('age', 18, 30);
插入数据
// 插入单条数据 DB::name('user_log') ->insert([ 'uid' => 123, 'score' => 99, 'date' => '2025-09-23', 'extra' => json_encode(['ip' => '1.1.1.1']) ]); // 批量插入 DB::name('user_log') ->insertAll([ ['uid' => 1, 'score' => 99, 'date' => '2025-09-23'], ['uid' => 2, 'score' => 88, 'date' => '2025-09-23'], ['uid' => 3, 'score' => 77, 'date' => '2025-09-23'], ]); // 替代插入(适用于 ReplacingMergeTree) DB::name('user_log') ->replace([ 'uid' => 123, 'name' => 'Tom', 'date' => '2025-09-23', ]);
更新数据
// 基本更新 DB::name('user_log') ->where('uid', '=', 123) ->update(['score' => 100]); // 字段增加值 DB::name('user_log') ->where('uid', '=', 123) ->setInc('score', 10); // 字段减少值 DB::name('user_log') ->where('uid', '=', 123) ->setDec('score', 5); // 批量增加多个字段的值 DB::name('user_log') ->where('uid', '=', 123) ->setInc(['score' => 10, 'views' => 1]); // 批量减少多个字段的值 DB::name('user_log') ->where('uid', '=', 123) ->setDec(['score' => 5, 'stock' => 1]);
JOIN 查询
// 基本 JOIN $data = DB::name('user_log l') ->join('users u', 'l.uid = u.id') ->field('u.name, sum(l.score) as total') ->group('u.name') ->select(); // LEFT JOIN $data = DB::name('user_log l') ->leftJoin('users u', 'l.uid = u.id') ->select(); // INNER JOIN $data = DB::name('user_log l') ->innerJoin('users u', 'l.uid = u.id') ->select(); // ASOF JOIN(ClickHouse 特有) $data = DB::name('user_log l') ->asofJoin('users u', 'l.uid = u.id') ->select();
子查询
// 子查询 $sub = DB::name('user_active')->field('uid')->where('active', 1); $data = DB::name('user_log') ->whereIn('uid', $sub) ->select(); // WITH 子句 $data = DB::name('user_log') ->with('active_users', DB::name('user_active')->where('active', 1)) ->join('active_users au', 'user_log.uid = au.uid') ->select();
原生 SQL
// 执行原生查询 $data = DB::query("SELECT * FROM user_log WHERE uid = ? AND date = ?", [123, '2025-09-23']); // 执行原生命令 $result = DB::execute("INSERT INTO user_log (uid, score) VALUES (?, ?)", [123, 99]); // 在构建器中使用原生表达式 $data = DB::name('user_log') ->where('date', '=', DB::raw("toDate('2025-09-23')")) ->select();
辅助函数
// 获取查询构建器实例 $query = clickhouse(); // 指定表名 $query = clickhouse_table('user_log'); // 执行原生查询 $data = clickhouse_query("SELECT * FROM user_log WHERE uid = ?", [123]); // 执行原生命令 $result = clickhouse_execute("INSERT INTO user_log (uid) VALUES (?)”, [123]); // 创建原生表达式 $expr = clickhouse_raw("toDate('2025-09-23')");
假事务机制
ClickHouse 的事务功能是实验性的,本组件提供假事务机制用于 API 兼容性:
// 假事务示例 DB::begin(); try { // 执行数据操作 DB::name('user_log')->insert(['uid' => 123, 'score' => 99]); // 提交事务 DB::commit(); } catch (Exception $e) { // 回滚事务(注意:这不会实际撤销已执行的操作) DB::rollback(); } // 嵌套事务 DB::begin(); try { // 外层操作 DB::name('table1')->insert(['name' => 'test']); DB::begin(); try { // 内层操作 DB::name('table2')->insert(['value' => 100]); DB::commit(); // 内层提交 } catch (Exception $e) { DB::rollback(); // 内层回滚 } DB::commit(); // 外层提交 } catch (Exception $e) { DB::rollback(); // 外层回滚 }
ClickHouse 特有功能
PREWHERE 优化
// 使用 PREWHERE 优化查询性能 $data = DB::name('user_log') ->prewhere('date', '=', '2025-09-23') ->where('uid', '>', 100) ->select();
批量插入优化
// 大批量数据插入 $rows = []; for ($i = 0; $i < 10000; $i++) { $rows[] = [ 'uid' => $i, 'score' => rand(1, 100), 'date' => '2025-09-23', ]; } DB::name('user_log')->insertAll($rows);
高级用法
多连接管理
// 使用指定连接 $data = DB::connection('analytics') ->name('user_log') ->select(); // 或者 $data = DB::name('user_log', 'analytics')->select();
调试模式
// 开启调试模式 $query = DB::connection()->debug(true); // 获取最后执行的 SQL $sql = DB::connection()->getLastSql();
注意事项
- ClickHouse 不支持 UPDATE 和 DELETE 操作(除非使用特殊引擎或 ALTER TABLE)
- 使用 TCP 协议需要安装 ext-clickhouse 扩展或 Swoole
- 大批量插入时建议使用 insertAll 方法,性能更好
- 本组件提供的事务机制是假事务,主要用于 API 兼容性,不提供真正的事务保证
- 在假事务中,调用
ROLLBACK()
不会实际撤销已执行的操作 - ClickHouse 中单个 INSERT/UPDATE/DELETE 操作本身具有原子性,但不支持跨多个操作的事务一致性
许可证
MIT