You need to enable JavaScript to run this app.
ByteHouse云数仓版

ByteHouse云数仓版

复制全文
使用驱动程序
ClickHouse PHP Driver
复制全文
ClickHouse PHP Driver

本文介绍如何在 PHP 开发环境连接并访问 ByteHouse 云数仓。

环境要求

细分项

已验证版本

ClickHouse 驱动程序版本

已验证版本:1.6.0,版本详情及下载链接请参见:phpClickhouse v1.6.0

PHP 版本

已验证版本:PHP 8.4 或更高版本

使用限制
  • 不支持 ByteHouse 的 JSONB、BitMap64 类型。
  • 不支持 KeepAlive 设置。
  • 如果您在使用过程中遇到其他未知限制,请联系 ByteHouse 团队处理。

安装驱动

获取 ClickHouse 驱动后,可以通过 composer 进行安装:

composer require smi2/phpclickhouse

获取 ByteHouse 连接信息

ByteHouse 支持通过 IAM 用户或数据库用户连接 ClickHouse PHP Driver。IAM 用户与数据库用户二者差异说明如下,您可按需选择。

  • IAM 用户为火山引擎访问控制(IAM)中创建的用户,其权限由 IAM 权限策略及您授予的 ByteHouse 资源和数据权限决定。IAM 用户可访问 ByteHouse 控制台,也支持通过 CLI、连接驱动、生态工具、API 等方式访问 ByteHouse。
  • 数据库用户为 ByteHouse 中创建的数据库级别用户,可为其授予环境、资源和数据权限。数据库用户不可访问 ByteHouse 控制台,但支持通过 CLI、连接驱动、生态工具、API 等方式访问 ByteHouse。

更多 IAM 用户和数据库用户的介绍请参见以下文档:

使用 IAM 用户连接

参数

配置要点

host

配置为 ByteHouse 的公网/私网连接域名,其中 {TENANT_ID}、{REGION} 分别为火山引擎主账号的账号 ID 和 ByteHouse 的地域信息,您可以在 ByteHouse 控制台的租户管理 > 基本信息 > 网络信息中查看并复制网络信息。详情请参见步骤二:配置网络信息

port

固定配置为:8123。

database

配置为连接 ByteHouse 的数据库名称。

user & password

  • user:固定配置为 bytehouse。
  • password:为 ByteHouse 的 <API_Key>,您可以在 ByteHouse 控制台的 租户管理>连接信息 中获取 API Key。详情请参见获取 API Key

https

固定配置为:true。

$db->settings()->set()

可选,用于设置 ByteHouse 服务器相关参数,例如,配置连接 ByteHouse 后使用的计算组。如果您有多个计算组,希望后续查询 ByteHouse 数据时使用指定计算组,或者希望设置其他指定参数,您可以在$db->settings()->set()中进行设置。
计算组获取方式:您可以在 ByteHouse 控制台的计算组页面查看对应计算组的 ID。

使用数据库用户连接

参数

配置要点

host

配置为 ByteHouse 的公网/私网连接域名,其中 {TENANT_ID}、{REGION} 分别为火山引擎主账号的账号 ID 和 ByteHouse 的地域信息,您可以在 ByteHouse 控制台的租户管理 > 基本信息 > 网络信息中查看并复制王阔信息。详情请参见步骤二:配置网络信息

port

固定配置为:8123。

database

配置为连接 ByteHouse 的数据库名称。

user & password

  • user 配置为 {accountID_or_accountName}::{username}[::{envID}],详情请参见步骤三:获取 ByteHouse 连接串信息
    • {accountID_or_accountName} :指火山引擎用户账号 ID 或名称,可登录 ByteHouse 控制台,单击右上角个人中心查看并复制账号 ID 或名称。
    • {username} :指登录 ByteHouse 数据库的用户名。可在 ByteHouse 控制台 > 权限管理 > 用户 > 查看数据库用户名
    • {envID}:可选配置,数据库所在的环境名称。如果使用 default 环境,可不配置;如需使用其他环境,需指定环境名称,配置时无需添加[]。您可登录 ByteHouse 控制台,在租户管理 > 基本信息 > 当前环境中获取。
      使用示例如下:
      • 配置环境 ID:21xxxxxxxx::demouser::demoenv
      • 不配置环境 ID:21xxxxxxxx::demouser
  • password:为 ByteHouse 数据库账号的密码,可联系管理员获取数据库账号的密码。如果密码丢失或遗忘,可通联系管理员重置密码,详情请参考重置密码

https

固定配置为:true。

$db->settings()->set()

可选,用于设置 ByteHouse 服务器相关参数,例如,配置连接 ByteHouse 后使用的计算组。如果您有多个计算组,希望后续查询 ByteHouse 数据时使用指定计算组,或者希望设置其他指定参数,您可以在$db->settings()->set()中进行设置。
计算组获取方式:您可以在 ByteHouse 控制台的计算组页面查看对应计算组的 ID。

基本用法

连接 ByteHouse 后,您可以通过 ClickHouse PHP Driver 进行建表、数据读写等操作,以下为简单的操作示例。

  • 超时时间配置:setConnectTimeOut 默认为 5s、setTimeout 默认为 20s、setTimeout 将调用 max_execution_time 的 ClickHouse 参数。

连接 ByteHouse

可参考下面代码连接至 ByteHouse,使用时注意替换连接语句中的 {Host}{User}{Password}{Database}{VIRTUAL_WAREHOUSE_ID} 等连接信息字段,获取方式请参见获取 ByteHouse 连接信息

<?php

require_once 'vendor/autoload.php';

use Ramsey\Uuid\Uuid;

class Example
{
    private $client;
    private $config;

    public function __construct($host, $port, $user, $password, $database, $virtualWarehouse = null)
    {
        $this->config = [
            'host' => $host,
            'port' => $port,
            'username' => $user,
            'password' => $password,
            'database' => $database,
            'https' => true
        ];

        $this->initializeClient($virtualWarehouse);
    }

    private function initializeClient($virtualWarehouse)
    {
        try {
            $this->client = new ClickHouseDB\Client($this->config);
            if ($virtualWarehouse) {
                $this->client->settings()->set('virtual_warehouse', $virtualWarehouse);
            }
            $this->client->ping();
            $this->client->setTimeout(60);
            $this->client->setConnectTimeOut(10);

        } catch (Exception $e) {
            throw new Exception("初始化ClickHouse客户端失败: " . $e->getMessage());
        }
    }

    public function __destruct()
    {
        unset($this->client);
    }
}

try {
    $host = "{Host}";
    $port = 8123;
    $password = "{Password}";
    $user = "{User}";
    $database = "{Database}";
    $virtual_warehouse_id = "{VIRTUAL_WAREHOUSE_ID}";

    $example = new Example($host, $port, $user, $password, $database, $virtual_warehouse_id);
    $example->runExample();

} catch (Exception $e) {
    echo "程序执行失败: " . $e->getTraceAsString() . "\n";
    exit(1);
}

?>

自定义 query ID

您可使用 settings 中的 set 设置 session 级别的 query ID。

注意

请注意这种方式下,需要每一个 SQL 执行之前重新设置 query ID。如果只执行一次 query ID,则所有的 SQL 都将会是重复的 query ID。

$this->client->settings()->set('query_id', 'customized_' . Uuid::uuid4()->toString());

建表、数据读写

<?php

require_once 'vendor/autoload.php';

use Ramsey\Uuid\Uuid;

class Example
{
    private $client;
    private $config;

    public function __construct($host, $port, $user, $password, $database, $virtualWarehouse = null)
    {
        $this->config = [
            'host' => $host,
            'port' => $port,
            'username' => $user,
            'password' => $password,
            'database' => $database,
            'https' => true
        ];

        $this->initializeClient($virtualWarehouse);
    }

    private function initializeClient($virtualWarehouse)
    {
        try {
            $this->client = new ClickHouseDB\Client($this->config);
            if ($virtualWarehouse) {
                $this->client->settings()->set('virtual_warehouse', $virtualWarehouse);
            }
            $this->client->ping();
            $this->client->setTimeout(60);
            $this->client->setConnectTimeOut(10);

        } catch (Exception $e) {
            throw new Exception("初始化ClickHouse客户端失败: " . $e->getMessage());
        }
    }

    public function runExample()
    {
        try {
            $this->cleanupDatabase();
            $this->createTable();
            $this->insertData();
            $this->queryData();
            $this->cleanupTable();

        } catch (Exception $e) {
            throw $e;
        }
    }

    private function cleanupDatabase()
    {
        $queryId = 'customized_' . Uuid::uuid4()->toString();
        $this->client->write("DROP DATABASE IF EXISTS bhpythontest");
        $this->client->write("CREATE DATABASE IF NOT EXISTS bhpythontest");
        $this->client->database('bhpythontest');
        echo "数据库清理和创建完成\n";
    }

    private function createTable()
    {
        $sql = "
        CREATE TABLE IF NOT EXISTS bhpythontest.example (
            Col1 UInt8,
            Col2 String,
            Col3 FixedString(3),
            Col4 UUID,
            Col5 Map(String, UInt8),
            Col6 Array(String),
            Col7 Tuple(String, UInt8, Array(Map(String, String))) KV,
            Col8 DateTime
        ) ENGINE = CnchMergeTree() ORDER BY tuple()
        ";

        $this->client->write($sql);
        echo "表创建成功\n";
    }

    private function insertData()
    {
        $start = microtime(true);
        $values = [];
        for ($i = 0; $i < 100; $i++) {
            $values[] = sprintf(
                "(42, 'ClickHouse', 'Inc', '%s', map('key', 1, 'key2', %d), ['Q','W','E','R','T','Y'], ('String Value', 5, [map('key','value')]), now())",
                Uuid::uuid4()->toString(),
                $i + 1
            );
        }

        $insertSQL = "INSERT INTO bhpythontest.example VALUES " . implode(', ', $values);
        $this->client->write($insertSQL);
        $time = microtime(true) - $start;
        echo sprintf("插入记录成功,耗时: %.2f 秒\n", $time);
    }

    private function queryData()
    {
        $result = $this->client->select("SELECT * FROM example LIMIT 5");

        if ($result->count() > 0) {
            echo "前5条记录:\n";
            echo str_repeat("-", 80) . "\n";

            foreach ($result->rows() as $index => $row) {
                echo sprintf("记录 %d:\n", $index + 1);
                echo sprintf("  Col1: %d\n", $row['Col1']);
                echo sprintf("  Col2: %s\n", $row['Col2']);
                echo sprintf("  Col3: %s\n", $row['Col3']);
                echo sprintf("  Col4: %s\n", $row['Col4']);
                echo sprintf("  Col5: %s\n", json_encode($row['Col5']));
                echo sprintf("  Col6: %s\n", json_encode($row['Col6']));
                echo sprintf("  Col7: %s\n", json_encode($row['Col7']));
                echo sprintf("  Col8: %s\n", $row['Col8']);
                echo "\n";
            }
        }
    }

    private function cleanupTable()
    {
        $this->client->write("DROP TABLE IF EXISTS example");
        echo "表清理完成\n";
    }

    public function __destruct()
    {
        unset($this->client);
    }
}

try {
    $host = "{Host}";
    $port = 8123;
    $password = "{Password}";
    $user = "{User}";
    $database = "{Database}";
    $virtual_warehouse_id = "{VIRTUAL_WAREHOUSE_ID}";

    $example = new Example($host, $port, $user, $password, $database, $virtual_warehouse_id);
    $example->runExample();

} catch (Exception $e) {
    echo "程序执行失败: " . $e->getTraceAsString() . "\n";
    exit(1);
}

?>

通过 CSV 文件批量写入数据

您可以使用insertBatchFiles()来并行插入多个 CSV 文件,可以有效地将数据批量加载到 ByteHouse。

<?php

require_once 'vendor/autoload.php';

use Ramsey\Uuid\Uuid;

function generateDemoCSV($filename, $numRows): void
{
    $fp = fopen($filename, 'w');
    for ($i = 0; $i < $numRows; $i++) {
        fputcsv($fp, [
            date('Y-m-d'), // event_date
            rand(1000, 9999), // user_id
            'Message ' . $i, // message
            rand(1, 100) // value
        ], ',', '"', "\\", "\n");
    }
    fclose($fp);
    echo "Generated $numRows rows in $filename\n";
}

class Example
{
    private $client;
    private $config;

    public function __construct($host, $port, $user, $password, $database, $virtualWarehouse = null)
    {
        $this->config = [
            'host' => $host,
            'port' => $port,
            'username' => $user,
            'password' => $password,
            'database' => $database,
            'https' => true
        ];

        $this->initializeClient($virtualWarehouse);
    }

    private function initializeClient($virtualWarehouse)
    {
        try {
            $this->client = new ClickHouseDB\Client($this->config);

            if ($virtualWarehouse) {
                $this->client->settings()->set('virtual_warehouse', $virtualWarehouse);
            }

            $this->client->ping();
            $this->client->setTimeout(60);
            $this->client->setConnectTimeOut(10);

        } catch (Exception $e) {
            throw new Exception("初始化ClickHouse客户端失败: " . $e->getMessage());
        }
    }

    public function runExample()
    {
        try {
            $this->cleanupDatabase();
            $this->insertIntoFromCSV();
        } catch (Exception $e) {
            throw $e;
        }
    }

    private function insertIntoFromCSV()
    {
        try {

            $this->client->write('DROP TABLE IF EXISTS demo_table');
            $this->client->write('
        CREATE TABLE demo_table (
            event_date Date,
            user_id UInt32,
            message String,
            value UInt32
        ) ENGINE = CnchMergeTree ORDER BY tuple()
    ');

            // Generate CSV files
            $files = [
                __DIR__ . '/demo_data_1.csv' => 1000,
                __DIR__ . '/demo_data_2.csv' => 1000
            ];

            foreach ($files as $file => $rows) {
                generateDemoCSV($file, $rows);
            }

            // Insert data via Batch Operation
            $result = $this->client->insertBatchFiles(
                'demo_table',
                array_keys($files),
                ['event_date', 'user_id', 'message', 'value']
            );

            $totalCount = $this->client->select('SELECT count() as count FROM demo_table')->fetchOne('count');
            echo "Total rows: $totalCount\n";

        } catch (\Exception $e) {
            echo "Error: " . $e->getMessage() . "\n";
        }
    }

    private function cleanupDatabase()
    {
        $this->client->write("DROP DATABASE IF EXISTS bhpythontest");
        $this->client->write("CREATE DATABASE IF NOT EXISTS bhpythontest");
        $this->client->database('bhpythontest');
        echo "数据库清理和创建完成\n";
    }

    public function __destruct()
    {

        unset($this->client);
    }
}


try {
    $host = "{Host}";
    $port = 8123;
    $password = "{Password}";
    $user = "{User}";
    $database = "{Database}";
    $virtual_warehouse_id = "{VIRTUAL_WAREHOUSE_ID}";


    $example = new Example($host, $port, $user, $password, $database, $virtual_warehouse_id);
    $example->runExample();

} catch (Exception $e) {
    echo "程序执行失败: " . $e->getTraceAsString() . "\n";
    exit(1);
}

?>

通过参数绑定查询数据

当前支持通过':param'语法、'{param}'语法,使用占位符安全地将参数值传递给查询,示例如下。

<?php

require_once 'vendor/autoload.php';

use Ramsey\Uuid\Uuid;

function generateDemoCSV($filename, $numRows): void
{
    $fp = fopen($filename, 'w');
    for ($i = 0; $i < $numRows; $i++) {
        fputcsv($fp, [
            date('Y-m-d'), // event_date
            rand(1000, 9999), // user_id
            'Message ' . $i, // message
            rand(1, 100) // value
        ], ',', '"', "\\", "\n");
    }
    fclose($fp);
    echo "Generated $numRows rows in $filename\n";
}

class Example
{
    private $client;
    private $config;

    public function __construct($host, $port, $user, $password, $database, $virtualWarehouse = null)
    {
        $this->config = [
            'host' => $host,
            'port' => $port,
            'username' => $user,
            'password' => $password,
            'database' => $database,
            'https' => true
        ];

        $this->initializeClient($virtualWarehouse);
    }

    private function initializeClient($virtualWarehouse)
    {
        try {
            $this->client = new ClickHouseDB\Client($this->config);

            if ($virtualWarehouse) {
                $this->client->settings()->set('virtual_warehouse', $virtualWarehouse);
            }

            $this->client->ping();
            $this->client->setTimeout(60);
            $this->client->setConnectTimeOut(10);

        } catch (Exception $e) {
            throw new Exception("初始化ClickHouse客户端失败: " . $e->getMessage());
        }
    }

    public function runExample()
    {
        try {
            $this->cleanupDatabase();
            $this->bindParamters();
        } catch (Exception $e) {
            throw $e;
        }
    }

    
    private function bindParamters()
    {
        try {
            // Create a sample table
            $this->client->write('DROP TABLE IF EXISTS user_events');
            $this->client->write('
        CREATE TABLE user_events (
            event_date Date,
            event_type String,
            user_id UInt32,
            score Float32,
            tags Array(String)
        ) ENGINE = CnchMergeTree ORDER BY tuple()
    ');
    
            // Insert sample data
            $this->client->insert('user_events', [
                [
                    '2024-01-01', 'login', 101, 0.5, ['new', 'mobile']
                ],
                [
                    '2024-01-01', 'purchase', 102, 0.8, ['vip', 'desktop']
                ],
                [
                    '2024-01-02', 'login', 101, 0.3, ['mobile']
                ]
            ], [
                'event_date', 'event_type', 'user_id', 'score', 'tags'
            ]);
    
            // 示例 1: 基础参数绑定
            $result = $this->client->select(
                'SELECT * FROM user_events WHERE user_id = :id',
                ['id' => 101]
            );
            echo "Events for user 101:\n";
            print_r($result->rows());
    
            // 示例 2: 多个参数绑定
            $result = $this->client->select(
                'SELECT * FROM user_events 
         WHERE event_date = :date AND event_type = :type',
                [
                    'date' => '2024-01-01',
                    'type' => 'login'
                ]
            );
            echo "\nLogin events on 2024-01-01:\n";
            print_r($result->rows());
    
            // 示例 3: 绑定 Array
            $result = $this->client->select(
                'SELECT * FROM user_events 
         WHERE user_id IN (:users) AND score >= :min_score',
                [
                    'users' => [101, 102],
                    'min_score' => 0.5
                ]
            );
            echo "\nHigh-scoring events for selected users:\n";
            print_r($result->rows());
    
            // 示例 4:为列查询绑定模板(template)
            $columns = 'event_date, event_type, user_id';
            $result = $this->client->select(
                'SELECT {cols} FROM user_events WHERE score > :score',
                [
                    'cols' => $columns,
                    'score' => 0.4
                ]
            );
            echo "\nSelected columns for high scores:\n";
            print_r($result->rows());
    
        } catch (\Exception $e) {
            echo "Error: " . $e->getMessage() . "\n";
        }
    
    }

    private function cleanupDatabase()
    {
        $this->client->write("DROP DATABASE IF EXISTS bhpythontest");
        $this->client->write("CREATE DATABASE IF NOT EXISTS bhpythontest");
        $this->client->database('bhpythontest');
        echo "数据库清理和创建完成\n";
    }

    public function __destruct()
    {

        unset($this->client);
    }
}


try {
    $host = "{Host}";
    $port = 8123;
    $password = "{Password}";
    $user = "{User}";
    $database = "{Database}";
    $virtual_warehouse_id = "{VIRTUAL_WAREHOUSE_ID}";


    $example = new Example($host, $port, $user, $password, $database, $virtual_warehouse_id);
    $example->runExample();

} catch (Exception $e) {
    echo "程序执行失败: " . $e->getTraceAsString() . "\n";
    exit(1);
}

?>
最近更新时间:2025.11.11 16:07:14
这个页面对您有帮助吗?
有用
有用
无用
无用