
TDengine Go 连接器入门指南
本指南专为 Go 语言初学者设计,通过实际案例帮助你快速上手 TDengine Go 连接器。
目录
环境准备
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, ¤t, &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: 如何提高批量插入性能?
答:
- 使用参数绑定(Prepared Statement)
- 使用批量插入语法(一次插入多行)
- 关闭自动提交(如适用)
- 使用原生连接而非 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" 错误怎么办?
答:
- 检查 TDengine 服务是否启动:
systemctl status taosd - 检查防火墙是否开放端口:6030(原生)/ 6041(WebSocket)
- 检查连接地址和端口是否正确
Q5: 如何处理 NULL 值?
答:
go
/* 使用 sql.NullXXX 类型 */
var (
ts time.Time
current sql.NullFloat64 /* 可能为 NULL */
voltage sql.NullInt32 /* 可能为 NULL */
)
err := rows.Scan(&ts, ¤t, &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 连接器的基础学习!接下来你可以:
- 📖 深入学习 TDengine SQL 语法
- 🔧 探索 数据订阅功能
- 📊 集成 Grafana 可视化
- 🚀 查看 性能优化指南
参考资源
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。