TDengine Go 连接器入门指南

TDengine Go 连接器入门指南

本指南专为 Go 语言初学者设计,通过实际案例帮助你快速上手 TDengine Go 连接器。

目录

  1. 环境准备
  2. 快速开始:第一个程序
  3. 基础操作
  4. 进阶技巧
  5. 常见问题

环境准备

1. 安装 TDengine

首先确保你已经安装并启动了 TDengine 服务。如果还没安装,请访问 TDengine 官网 下载安装。

2. 创建 Go 项目

bash 复制代码
# 创建项目目录
mkdir tdengine-go-demo
cd tdengine-go-demo

# 初始化 Go 模块
go mod init tdengine-demo

3. 安装 Go 连接器

TDengine 提供了两种连接方式:

方式一:原生连接(推荐,性能最好)
bash 复制代码
go get github.com/taosdata/driver-go/v3/taosSql

注意:原生连接需要先安装 TDengine 客户端驱动(taosc)。

方式二:WebSocket 连接(无需安装客户端)
bash 复制代码
go get github.com/taosdata/driver-go/v3/taosWS

优点:跨平台,无需安装额外驱动,适合容器化部署。


快速开始:第一个程序

示例 1:连接数据库并执行简单查询

使用原生连接
go 复制代码
package main

import (
    "database/sql"
    "fmt"
    "log"
    
    _ "github.com/taosdata/driver-go/v3/taosSql"
)

func main() {
    // 连接数据库
    // 格式:用户名:密码@协议(地址:端口)/数据库名
    db, err := sql.Open("taosSql", "root:taosdata@tcp(localhost:6030)/")
    if err != nil {
        log.Fatal("连接失败:", err)
    }
    defer db.Close()

    // 测试连接
    err = db.Ping()
    if err != nil {
        log.Fatal("无法连接到 TDengine:", err)
    }

    fmt.Println("✓ 成功连接到 TDengine!")

    // 查询服务器版本
    var version string
    err = db.QueryRow("SELECT server_version()").Scan(&version)
    if err != nil {
        log.Fatal("查询失败:", err)
    }

    fmt.Printf("TDengine 版本: %s\n", version)
}
使用 WebSocket 连接
go 复制代码
package main

import (
    "database/sql"
    "fmt"
    "log"
    
    _ "github.com/taosdata/driver-go/v3/taosWS"
)

func main() {
    // WebSocket 连接
    // 格式:用户名:密码@ws(地址:端口)/数据库名
    db, err := sql.Open("taosWS", "root:taosdata@ws(localhost:6041)/")
    if err != nil {
        log.Fatal("连接失败:", err)
    }
    defer db.Close()

    fmt.Println("✓ 成功连接到 TDengine (WebSocket)!")
}

运行程序

bash 复制代码
go run main.go

基础操作

示例 2:创建数据库和表

go 复制代码
package main

import (
    "database/sql"
    "fmt"
    "log"
    
    _ "github.com/taosdata/driver-go/v3/taosSql"
)

func main() {
    db, err := sql.Open("taosSql", "root:taosdata@tcp(localhost:6030)/")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // 1. 创建数据库
    _, err = db.Exec("CREATE DATABASE IF NOT EXISTS test_db")
    if err != nil {
        log.Fatal("创建数据库失败:", err)
    }
    fmt.Println("✓ 数据库创建成功")

    // 2. 使用数据库
    _, err = db.Exec("USE test_db")
    if err != nil {
        log.Fatal("切换数据库失败:", err)
    }

    // 3. 创建超级表(模板表)
    createSTableSQL := `
        CREATE STABLE IF NOT EXISTS meters (
            ts TIMESTAMP,           /* 时间戳(主键) */
            current FLOAT,          /* 电流 */
            voltage INT,            /* 电压 */
            phase FLOAT             /* 相位 */
        ) TAGS (
            location BINARY(64),    /* 位置标签 */
            groupId INT             /* 分组标签 */
        )
    `
    _, err = db.Exec(createSTableSQL)
    if err != nil {
        log.Fatal("创建超级表失败:", err)
    }
    fmt.Println("✓ 超级表创建成功")

    // 4. 创建子表(实际存储数据的表)
    _, err = db.Exec(`
        CREATE TABLE IF NOT EXISTS d1001 USING meters 
        TAGS ('Beijing.Chaoyang', 1)
    `)
    if err != nil {
        log.Fatal("创建子表失败:", err)
    }
    fmt.Println("✓ 子表创建成功")
}

示例 3:插入数据

go 复制代码
package main

import (
    "database/sql"
    "fmt"
    "log"
    "time"
    
    _ "github.com/taosdata/driver-go/v3/taosSql"
)

func main() {
    db, err := sql.Open("taosSql", "root:taosdata@tcp(localhost:6030)/test_db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // 方法 1:插入单条数据
    insertSQL := `
        INSERT INTO d1001 
        VALUES (?, ?, ?, ?)
    `
    
    now := time.Now()
    result, err := db.Exec(insertSQL, now, 10.2, 219, 0.32)
    if err != nil {
        log.Fatal("插入数据失败:", err)
    }
    
    rowsAffected, _ := result.RowsAffected()
    fmt.Printf("✓ 插入成功,影响行数: %d\n", rowsAffected)

    // 方法 2:批量插入
    batchInsertSQL := `
        INSERT INTO d1001 VALUES 
        (NOW, 10.3, 219, 0.31)
        (NOW + 1s, 12.6, 218, 0.33)
        (NOW + 2s, 11.5, 221, 0.35)
    `
    
    result, err = db.Exec(batchInsertSQL)
    if err != nil {
        log.Fatal("批量插入失败:", err)
    }
    
    rowsAffected, _ = result.RowsAffected()
    fmt.Printf("✓ 批量插入成功,影响行数: %d\n", rowsAffected)

    // 方法 3:自动建表插入
    autoCreateSQL := `
        INSERT INTO d1002 USING meters TAGS ('Beijing.Haidian', 2) 
        VALUES (NOW, 10.1, 220, 0.33)
    `
    
    _, err = db.Exec(autoCreateSQL)
    if err != nil {
        log.Fatal("自动建表插入失败:", err)
    }
    fmt.Println("✓ 自动建表插入成功")
}

示例 4:查询数据

go 复制代码
package main

import (
    "database/sql"
    "fmt"
    "log"
    "time"
    
    _ "github.com/taosdata/driver-go/v3/taosSql"
)

func main() {
    db, err := sql.Open("taosSql", "root:taosdata@tcp(localhost:6030)/test_db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // 查询数据
    rows, err := db.Query("SELECT ts, current, voltage, phase FROM d1001 ORDER BY ts DESC LIMIT 10")
    if err != nil {
        log.Fatal("查询失败:", err)
    }
    defer rows.Close()

    fmt.Println("查询结果:")
    fmt.Println("时间\t\t\t\t电流\t电压\t相位")
    fmt.Println("----------------------------------------")

    // 遍历结果集
    for rows.Next() {
        var (
            ts      time.Time
            current float32
            voltage int32
            phase   float32
        )
        
        err := rows.Scan(&ts, &current, &voltage, &phase)
        if err != nil {
            log.Fatal("读取数据失败:", err)
        }
        
        fmt.Printf("%s\t%.2f\t%d\t%.2f\n", 
            ts.Format("2006-01-02 15:04:05"), current, voltage, phase)
    }

    // 检查遍历过程中的错误
    if err = rows.Err(); err != nil {
        log.Fatal("遍历结果集出错:", err)
    }
}

示例 5:聚合查询

go 复制代码
package main

import (
    "database/sql"
    "fmt"
    "log"
    
    _ "github.com/taosdata/driver-go/v3/taosSql"
)

func main() {
    db, err := sql.Open("taosSql", "root:taosdata@tcp(localhost:6030)/test_db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // 统计查询
    var (
        avgCurrent float64
        maxVoltage int32
        minVoltage int32
        count      int64
    )

    query := `
        SELECT 
            AVG(current) as avg_current,
            MAX(voltage) as max_voltage,
            MIN(voltage) as min_voltage,
            COUNT(*) as total_count
        FROM d1001
    `

    err = db.QueryRow(query).Scan(&avgCurrent, &maxVoltage, &minVoltage, &count)
    if err != nil {
        log.Fatal("查询失败:", err)
    }

    fmt.Println("统计结果:")
    fmt.Printf("平均电流: %.2f A\n", avgCurrent)
    fmt.Printf("最大电压: %d V\n", maxVoltage)
    fmt.Printf("最小电压: %d V\n", minVoltage)
    fmt.Printf("记录总数: %d\n", count)

    // 按时间窗口聚合
    fmt.Println("\n按 10 秒窗口聚合:")
    rows, err := db.Query(`
        SELECT 
            _WSTART as window_start,
            AVG(current) as avg_current,
            COUNT(*) as count
        FROM d1001
        INTERVAL(10s)
        LIMIT 5
    `)
    if err != nil {
        log.Fatal("窗口查询失败:", err)
    }
    defer rows.Close()

    for rows.Next() {
        var windowStart string
        var avgCurrent float64
        var count int64
        
        rows.Scan(&windowStart, &avgCurrent, &count)
        fmt.Printf("窗口: %s, 平均电流: %.2f, 记录数: %d\n", 
            windowStart, avgCurrent, count)
    }
}

进阶技巧

示例 6:使用参数绑定(提升性能)

参数绑定(Prepared Statement)可以提高批量插入的性能,并防止 SQL 注入。

go 复制代码
package main

import (
    "database/sql"
    "fmt"
    "log"
    "time"
    
    _ "github.com/taosdata/driver-go/v3/taosSql"
)

func main() {
    db, err := sql.Open("taosSql", "root:taosdata@tcp(localhost:6030)/test_db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // 准备 SQL 语句
    stmt, err := db.Prepare("INSERT INTO d1001 VALUES (?, ?, ?, ?)")
    if err != nil {
        log.Fatal("准备语句失败:", err)
    }
    defer stmt.Close()

    // 批量插入数据
    startTime := time.Now()
    
    for i := 0; i < 100; i++ {
        timestamp := time.Now().Add(time.Duration(i) * time.Second)
        current := 10.0 + float32(i)*0.1
        voltage := 220 + i%10
        phase := 0.3 + float32(i)*0.01
        
        _, err = stmt.Exec(timestamp, current, voltage, phase)
        if err != nil {
            log.Printf("插入第 %d 条数据失败: %v\n", i, err)
            continue
        }
    }

    elapsed := time.Since(startTime)
    fmt.Printf("✓ 使用参数绑定插入 100 条数据,耗时: %s\n", elapsed)
}

示例 7:错误处理最佳实践

go 复制代码
package main

import (
    "database/sql"
    "fmt"
    "log"
    
    _ "github.com/taosdata/driver-go/v3/taosSql"
    "github.com/taosdata/driver-go/v3/errors"
)

func main() {
    db, err := sql.Open("taosSql", "root:taosdata@tcp(localhost:6030)/test_db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // 尝试创建已存在的表(会产生错误)
    _, err = db.Exec("CREATE TABLE d1001 (ts TIMESTAMP, val INT)")
    
    if err != nil {
        // 判断是否为 TDengine 错误
        if tError, ok := err.(*errors.TaosError); ok {
            fmt.Printf("TDengine 错误码: %d\n", int(tError.Code))
            fmt.Printf("错误信息: %s\n", tError.ErrStr)
            
            // 根据错误码做不同处理
            if tError.Code == 0x2603 { /* 表已存在 */
                fmt.Println("表已存在,继续执行...")
            } else {
                log.Fatal("未知 TDengine 错误")
            }
        } else {
            // 其他类型错误
            log.Fatal("系统错误:", err)
        }
    }
}

示例 8:完整的 CRUD 操作封装

go 复制代码
package main

import (
    "database/sql"
    "fmt"
    "log"
    "time"
    
    _ "github.com/taosdata/driver-go/v3/taosSql"
)

// MeterData 表示传感器数据
type MeterData struct {
    Timestamp time.Time
    Current   float32
    Voltage   int32
    Phase     float32
}

// TDengineClient 封装 TDengine 操作
type TDengineClient struct {
    db *sql.DB
}

// NewTDengineClient 创建客户端
func NewTDengineClient(dsn string) (*TDengineClient, error) {
    db, err := sql.Open("taosSql", dsn)
    if err != nil {
        return nil, err
    }
    
    if err := db.Ping(); err != nil {
        return nil, err
    }
    
    return &TDengineClient{db: db}, nil
}

// Close 关闭连接
func (c *TDengineClient) Close() error {
    return c.db.Close()
}

// InsertData 插入数据
func (c *TDengineClient) InsertData(table string, data MeterData) error {
    sql := fmt.Sprintf("INSERT INTO %s VALUES (?, ?, ?, ?)", table)
    _, err := c.db.Exec(sql, data.Timestamp, data.Current, data.Voltage, data.Phase)
    return err
}

// QueryLatest 查询最新数据
func (c *TDengineClient) QueryLatest(table string, limit int) ([]MeterData, error) {
    sql := fmt.Sprintf("SELECT ts, current, voltage, phase FROM %s ORDER BY ts DESC LIMIT ?", table)
    rows, err := c.db.Query(sql, limit)
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    var results []MeterData
    for rows.Next() {
        var data MeterData
        err := rows.Scan(&data.Timestamp, &data.Current, &data.Voltage, &data.Phase)
        if err != nil {
            return nil, err
        }
        results = append(results, data)
    }
    
    return results, rows.Err()
}

// GetAverage 获取平均值
func (c *TDengineClient) GetAverage(table string) (float64, error) {
    sql := fmt.Sprintf("SELECT AVG(current) FROM %s", table)
    var avg float64
    err := c.db.QueryRow(sql).Scan(&avg)
    return avg, err
}

func main() {
    // 创建客户端
    client, err := NewTDengineClient("root:taosdata@tcp(localhost:6030)/test_db")
    if err != nil {
        log.Fatal("连接失败:", err)
    }
    defer client.Close()

    // 插入数据
    data := MeterData{
        Timestamp: time.Now(),
        Current:   10.5,
        Voltage:   220,
        Phase:     0.33,
    }
    
    err = client.InsertData("d1001", data)
    if err != nil {
        log.Fatal("插入失败:", err)
    }
    fmt.Println("✓ 数据插入成功")

    // 查询最新数据
    results, err := client.QueryLatest("d1001", 5)
    if err != nil {
        log.Fatal("查询失败:", err)
    }
    
    fmt.Println("\n最新 5 条数据:")
    for i, r := range results {
        fmt.Printf("%d. %s - 电流:%.2f 电压:%d 相位:%.2f\n", 
            i+1, r.Timestamp.Format("15:04:05"), r.Current, r.Voltage, r.Phase)
    }

    // 获取平均值
    avg, err := client.GetAverage("d1001")
    if err != nil {
        log.Fatal("统计失败:", err)
    }
    fmt.Printf("\n平均电流: %.2f A\n", avg)
}

常见问题

Q1: 原生连接和 WebSocket 连接如何选择?

答:

  • 原生连接:性能最好,推荐用于生产环境,但需要安装 TDengine 客户端驱动
  • WebSocket 连接 :跨平台,无需安装额外驱动,适合:
    • 容器化部署
    • 无法安装客户端的环境
    • 快速原型开发

Q2: 时区如何处理?

答:

go 复制代码
/* 方法 1: 在 DSN 中指定时区(v3.7.4+) */
db, err := sql.Open("taosSql", "root:taosdata@tcp(localhost:6030)/?timezone=Asia%2FShanghai")

/* 方法 2: 使用 af 包设置时区 */
import "github.com/taosdata/driver-go/v3/af"

conn, err := af.Open("localhost", "root", "taosdata", "test_db", 6030)
err = conn.SetTimezone("Asia/Shanghai")

Q3: 如何提高批量插入性能?

答:

  1. 使用参数绑定(Prepared Statement)
  2. 使用批量插入语法(一次插入多行)
  3. 关闭自动提交(如适用)
  4. 使用原生连接而非 WebSocket
go 复制代码
/* 推荐:批量插入 */
sql := `
    INSERT INTO d1001 VALUES 
    (NOW, 10.3, 219, 0.31)
    (NOW + 1s, 12.6, 218, 0.33)
    (NOW + 2s, 11.5, 221, 0.35)
`
db.Exec(sql)

Q4: 遇到 "connection refused" 错误怎么办?

答:

  1. 检查 TDengine 服务是否启动:systemctl status taosd
  2. 检查防火墙是否开放端口:6030(原生)/ 6041(WebSocket)
  3. 检查连接地址和端口是否正确

Q5: 如何处理 NULL 值?

答:

go 复制代码
/* 使用 sql.NullXXX 类型 */
var (
    ts      time.Time
    current sql.NullFloat64  /* 可能为 NULL */
    voltage sql.NullInt32    /* 可能为 NULL */
)

err := rows.Scan(&ts, &current, &voltage)

if current.Valid {
    fmt.Printf("电流: %.2f\n", current.Float64)
} else {
    fmt.Println("电流: NULL")
}

Q6: 密码中包含特殊字符怎么办?

答:

go 复制代码
import "net/url"

password := "p@ssw0rd!#"
encodedPassword := url.QueryEscape(password)
dsn := fmt.Sprintf("root:%s@tcp(localhost:6030)/", encodedPassword)

下一步

恭喜你完成了 TDengine Go 连接器的基础学习!接下来你可以:

  1. 📖 深入学习 TDengine SQL 语法
  2. 🔧 探索 数据订阅功能
  3. 📊 集成 Grafana 可视化
  4. 🚀 查看 性能优化指南

参考资源


关于 TDengine

TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。

相关推荐
E_ICEBLUE4 分钟前
PPT 智能提取与分析实战:把演示文档变成结构化数据
数据库·python·powerpoint
困知勉行19858 分钟前
Redis数据结构及其底层实现
数据库·redis·缓存
豆豆13 分钟前
2026年建设网站的十个步骤
大数据·cms·网站建设·网站制作·低代码平台·建站·网站设计
一直在追15 分钟前
告别 WHERE id=1!大数据工程师的 AI 觉醒:手把手带你拆解向量数据库 (RAG 核心)
大数据·数据库
羊羊羊i19 分钟前
使用client-go访问k8s集群
golang·kubernetes
Gofarlic_OMS20 分钟前
协同设计平台中PTC许可证的高效调度策略
网络·数据库·安全·oracle·aigc
北京耐用通信20 分钟前
耐达讯自动化Profibus三路中继器:低成本搞定阀门定位器稳定组网的硬核方案
人工智能·物联网·自动化
新芒21 分钟前
海尔智家加速全球体育营销
大数据·人工智能
AAAAA924022 分钟前
物联网赋能新能源汽车:技术融合与产业变革
物联网·汽车
刘一说23 分钟前
Windows 与 Linux 跨平台自动化 MySQL 8 备份:专业级脚本设计与实战指南
linux·数据库·windows·mysql·自动化