PostgreSQL教程FG143-PG与Go对接:pq库核心用法
本文档风哥主要介绍PostgreSQL与Go的对接方法,重点关注pq库的核心用法。风哥教程参考PostgreSQL官方文档Client Interfaces部分的Go相关内容,适合开发人员在学习和测试中使用,如果要应用于生产环境则需要自行确认。更多视频教程www.fgedu.net.cn
Part01-基础概念与理论知识
1.1 pq库的概念
pq是PostgreSQL的Go语言驱动,它实现了database/sql包的接口,允许Go应用程序连接到PostgreSQL数据库并执行SQL语句。
- 完全实现了database/sql包的接口
- 支持PostgreSQL的所有主要特性
- 性能优异,适合生产环境使用
- 开源维护,社区活跃
1.2 pq库的核心特性
pq库的核心特性包括:
- 连接管理:创建和管理数据库连接
- SQL执行:执行SQL语句和存储过程
- 事务管理:支持事务的提交和回滚
- 参数化查询:防止SQL注入
- 批量操作:支持批量插入和更新
- 类型转换:自动在Go类型和PostgreSQL类型之间转换
1.3 Go的database/sql包
Go的database/sql包是Go语言的标准数据库接口,它提供了一套通用的数据库操作API,支持各种数据库驱动。
Part02-生产环境规划与建议
2.1 pq库安装
pq库的安装方法:
$ go get github.com/lib/pq
# 在go.mod文件中添加依赖
require github.com/lib/pq v1.10.9
2.2 连接设置
连接设置参数:
- 连接字符串:格式为postgres://user:password@host:port/database?sslmode=mode
- sslmode:SSL连接模式,如disable、require、verify-ca等
- 其他参数:如connect_timeout、fgapplication_name等
2.3 性能考虑因素
性能考虑因素:
- 连接池:使用连接池管理连接
- 批量操作:使用批处理减少网络往返
- 预编译语句:使用Prepare提高性能
- 并发处理:利用Go的并发特性提高性能
Part03-生产环境项目实施方案
3.1 基础操作
3.1.1 连接数据库
import (
“database/sql”
“fmt”
_ “github.com/lib/pq”
)
func main() {
// 连接数据库
connStr := “postgres://fgedu:fgedu123@fgedu.localhost:5432/fgedudb?sslmode=disable”
db, err := sql.Open(“postgres”, connStr)
if err != nil {
fmt.Println(“连接失败:”, err)
return
}
defer db.Close()
// 测试连接
err = db.Ping()
if err != nil {
fmt.Println(“Ping失败:”, err)
return
}
fmt.Println(“连接成功”)
}
// 输出示例
连接成功
3.1.2 执行SQL查询
import (
“database/sql”
“fmt”
_ “github.com/lib/pq”
)
func main() {
// 连接数据库
connStr := “postgres://fgedu:fgedu123@fgedu.localhost:5432/fgedudb?sslmode=disable”
db, err := sql.Open(“postgres”, connStr)
if err != nil {
fmt.Println(“连接失败:”, err)
return
}
defer db.Close()
// 执行查询
rows, err := db.Query(“SELECT * FROM fgedu_users”)
if err != nil {
fmt.Println(“查询失败:”, err)
return
}
defer rows.Close()
// 处理结果
for rows.Next() {
var id int
var name, email string
err := rows.Scan(&id, &name, &email)
if err != nil {
fmt.Println(“扫描失败:”, err)
return
}
fmt.Printf(“ID: %d, Name: %s, Email: %s\n”, id, name, email)
}
if err := rows.Err(); err != nil {
fmt.Println(“遍历结果失败:”, err)
return
}
}
// 输出示例
ID: 1, Name: 张三, Email: zhangsan@fgedu.net.cn
ID: 2, Name: 李四, Email: lisi@fgedu.net.cn
ID: 3, Name: 王五, Email: wangwu@fgedu.net.cn
3.1.3 参数化查询
import (
“database/sql”
“fmt”
_ “github.com/lib/pq”
)
func main() {
// 连接数据库
connStr := “postgres://fgedu:fgedu123@fgedu.localhost:5432/fgedudb?sslmode=disable”
db, err := sql.Open(“postgres”, connStr)
if err != nil {
fmt.Println(“连接失败:”, err)
return
}
defer db.Close()
// 执行参数化查询
var name, email string
err = db.QueryRow(“SELECT name, email FROM fgedu_users WHERE id = $1”, 1).Scan(&name, &email)
if err != nil {
fmt.Println(“查询失败:”, err)
return
}
fmt.Printf(“Name: %s, Email: %s\n”, name, email)
}
// 输出示例
Name: 张三, Email: zhangsan@fgedu.net.cn
3.2 高级操作
3.2.1 执行更新操作
import (
“database/sql”
“fmt”
_ “github.com/lib/pq”
)
func main() {
// 连接数据库
connStr := “postgres://fgedu:fgedu123@fgedu.localhost:5432/fgedudb?sslmode=disable”
db, err := sql.Open(“postgres”, connStr)
if err != nil {
fmt.Println(“连接失败:”, err)
return
}
defer db.Close()
// 执行更新
result, err := db.Exec(“UPDATE fgedu_users SET name = $1 WHERE id = $2”, “张三更新”, 1)
if err != nil {
fmt.Println(“更新失败:”, err)
return
}
// 获取影响行数
rowsAffected, err := result.RowsAffected()
if err != nil {
fmt.Println(“获取影响行数失败:”, err)
return
}
fmt.Printf(“更新了 %d 行\n”, rowsAffected)
}
// 输出示例
更新了 1 行
3.2.2 执行插入操作
import (
“database/sql”
“fmt”
_ “github.com/lib/pq”
)
func main() {
// 连接数据库
connStr := “postgres://fgedu:fgedu123@fgedu.localhost:5432/fgedudb?sslmode=disable”
db, err := sql.Open(“postgres”, connStr)
if err != nil {
fmt.Println(“连接失败:”, err)
return
}
defer db.Close()
// 执行插入
result, err := db.Exec(“INSERT INTO fgedu_users (name, email) VALUES ($1, $2)”, “赵六”, “zhaoliu@fgedu.net.cn”)
if err != nil {
fmt.Println(“插入失败:”, err)
return
}
// 获取插入的ID
id, err := result.LastInsertId()
if err != nil {
fmt.Println(“获取LastInsertId失败:”, err)
return
}
fmt.Printf(“插入成功,ID: %d\n”, id)
}
// 输出示例
插入成功,ID: 4
3.3 事务管理
import (
“database/sql”
“fmt”
_ “github.com/lib/pq”
)
func main() {
// 连接数据库
connStr := “postgres://fgedu:fgedu123@fgedu.localhost:5432/fgedudb?sslmode=disable”
db, err := sql.Open(“postgres”, connStr)
if err != nil {
fmt.Println(“连接失败:”, err)
return
}
defer db.Close()
// 开始事务
tx, err := db.Begin()
if err != nil {
fmt.Println(“开始事务失败:”, err)
return
}
// 执行第一个更新
_, err = tx.Exec(“UPDATE fgedu_users SET name = $1 WHERE id = $2”, “张三事务更新”, 1)
if err != nil {
tx.Rollback()
fmt.Println(“更新失败:”, err)
return
}
// 执行第二个更新
_, err = tx.Exec(“UPDATE fgedu_users SET name = $1 WHERE id = $2”, “李四事务更新”, 2)
if err != nil {
tx.Rollback()
fmt.Println(“更新失败:”, err)
return
}
// 提交事务
err = tx.Commit()
if err != nil {
fmt.Println(“提交事务失败:”, err)
return
}
fmt.Println(“事务提交成功”)
}
// 输出示例
事务提交成功
3.4 批量处理
import (
“database/sql”
“fmt”
_ “github.com/lib/pq”
)
func main() {
// 连接数据库
connStr := “postgres://fgedu:fgedu123@fgedu.localhost:5432/fgedudb?sslmode=disable”
db, err := sql.Open(“postgres”, connStr)
if err != nil {
fmt.Println(“连接失败:”, err)
return
}
defer db.Close()
// 开始事务
tx, err := db.Begin()
if err != nil {
fmt.Println(“开始事务失败:”, err)
return
}
// 准备语句
stmt, err := tx.Prepare(“INSERT INTO fgedu_users (name, email) VALUES ($1, $2)”)
if err != nil {
tx.Rollback()
fmt.Println(“准备语句失败:”, err)
return
}
defer stmt.Close()
// 批量插入
users := []struct {
name string
email string
}{
{“孙七”, “sunqi@fgedu.net.cn”},
{“周八”, “zhouba@fgedu.net.cn”},
{“吴九”, “wuju@fgedu.net.cn”},
}
for _, user := range users {
_, err := stmt.Exec(user.name, user.email)
if err != nil {
tx.Rollback()
fmt.Println(“插入失败:”, err)
return
}
}
// 提交事务
err = tx.Commit()
if err != nil {
fmt.Println(“提交事务失败:”, err)
return
}
fmt.Printf(“批量插入成功,共插入 %d 条记录\n”, len(users))
}
// 输出示例
批量插入成功,共插入 3 条记录
Part04-生产案例与实战讲解
4.1 基础示例
4.1.1 用户管理系统
import (
“database/sql”
“fmt”
_ “github.com/lib/pq”
)
type User struct {
ID int
Name string
Email string
}
type UserManager struct {
db *sql.DB
}
func NewUserManager(connStr string) (*UserManager, error) {
db, err := sql.Open(“postgres”, connStr)
if err != nil {
return nil, err
}
err = db.Ping()
if err != nil {
db.Close()
return nil, err
}
return &UserManager{db: db}, nil
}
func (um *UserManager) Close() error {
return um.db.Close()
}
func (um *UserManager) AddUser(name, email string) (int, error) {
var id int
err := um.db.QueryRow(“INSERT INTO fgedu_users (name, email) VALUES ($1, $2) RETURNING id”, name, email).Scan(&id)
if err != nil {
return 0, err
}
return id, nil
}
func (um *UserManager) GetUser(id int) (*User, error) {
var user User
err := um.db.QueryRow(“SELECT id, name, email FROM fgedu_users WHERE id = $1”, id).Scan(&user.ID, &user.Name, &user.Email)
if err != nil {
return nil, err
}
return &user, nil
}
func (um *UserManager) UpdateUser(id int, name, email string) error {
_, err := um.db.Exec(“UPDATE fgedu_users SET name = $1, email = $2 WHERE id = $3”, name, email, id)
return err
}
func (um *UserManager) DeleteUser(id int) error {
_, err := um.db.Exec(“DELETE FROM fgedu_users WHERE id = $1”, id)
return err
}
func (um *UserManager) GetAllUsers() ([]*User, error) {
rows, err := um.db.Query(“SELECT id, name, email FROM fgedu_users”)
if err != nil {
return nil, err
}
defer rows.Close()
users := make([]*User, 0)
for rows.Next() {
var user User
err := rows.Scan(&user.ID, &user.Name, &user.Email)
if err != nil {
return nil, err
}
users = fgappend(users, &user)
}
if err := rows.Err(); err != nil {
return nil, err
}
return users, nil
}
func main() {
connStr := “postgres://fgedu:fgedu123@fgedu.localhost:5432/fgedudb?sslmode=disable”
manager, err := NewUserManager(connStr)
if err != nil {
fmt.Println(“创建UserManager失败:”, err)
return
}
defer manager.Close()
// 添加用户
id, err := manager.AddUser(“钱十”, “qianshi@fgedu.net.cn”)
if err != nil {
fmt.Println(“添加用户失败:”, err)
return
}
fmt.Printf(“添加用户成功,ID: %d\n”, id)
// 获取用户
user, err := manager.GetUser(id)
if err != nil {
fmt.Println(“获取用户失败:”, err)
return
}
fmt.Printf(“获取用户成功: ID=%d, Name=%s, Email=%s\n”, user.ID, user.Name, user.Email)
// 更新用户
err = manager.UpdateUser(id, “钱十更新”, “qianshi_updated@fgedu.net.cn”)
if err != nil {
fmt.Println(“更新用户失败:”, err)
return
}
fmt.Println(“更新用户成功”)
// 获取所有用户
users, err := manager.GetAllUsers()
if err != nil {
fmt.Println(“获取所有用户失败:”, err)
return
}
fmt.Println(“获取所有用户成功:”)
for _, u := range users {
fmt.Printf(“ID: %d, Name: %s, Email: %s\n”, u.ID, u.Name, u.Email)
}
}
// 输出示例
添加用户成功,ID: 7
获取用户成功: ID=7, Name=钱十, Email=qianshi@fgedu.net.cn
更新用户成功
获取所有用户成功:
ID: 1, Name: 张三事务更新, Email: zhangsan@fgedu.net.cn
ID: 2, Name: 李四事务更新, Email: lisi@fgedu.net.cn
ID: 3, Name: 王五, Email: wangwu@fgedu.net.cn
ID: 4, Name: 赵六, Email: zhaoliu@fgedu.net.cn
ID: 5, Name: 孙七, Email: sunqi@fgedu.net.cn
ID: 6, Name: 周八, Email: zhouba@fgedu.net.cn
ID: 7, Name: 钱十更新, Email: qianshi_updated@fgedu.net.cn
4.2 连接池示例
4.2.1 使用连接池
import (
“database/sql”
“fmt”
“time”
_ “github.com/lib/pq”
)
func main() {
// 连接数据库
connStr := “postgres://fgedu:fgedu123@fgedu.localhost:5432/fgedudb?sslmode=disable”
db, err := sql.Open(“postgres”, connStr)
if err != nil {
fmt.Println(“连接失败:”, err)
return
}
defer db.Close()
// 配置连接池
db.SetMaxOpenConns(10) // 最大打开连接数
db.SetMaxIdleConns(5) // 最大空闲连接数
db.SetConnMaxLifetime(time.Hour) // 连接最大生命周期
// 测试连接池
for i := 0; i < 20; i++ {
go func(i int) {
rows, err := db.Query("SELECT * FROM fgedu_users WHERE id = $1", (i%7)+1)
if err != nil {
fmt.Printf("查询失败 %d: %v\n", i, err)
return
}
defer rows.Close()
for rows.Next() {
var id int
var name, email string
err := rows.Scan(&id, &name, &email)
if err != nil {
fmt.Printf("扫描失败 %d: %v\n", i, err)
return
}
fmt.Printf("Goroutine %d: ID: %d, Name: %s, Email: %s\n", i, id, name, email)
}
}(i)
}
// 等待所有goroutine完成
time.Sleep(2 * time.Second)
fmt.Println("测试完成")
}
// 输出示例
Goroutine 0: ID: 1, Name: 张三事务更新, Email: zhangsan@fgedu.net.cn
Goroutine 1: ID: 2, Name: 李四事务更新, Email: lisi@fgedu.net.cn
Goroutine 2: ID: 3, Name: 王五, Email: wangwu@fgedu.net.cn
Goroutine 3: ID: 4, Name: 赵六, Email: zhaoliu@fgedu.net.cn
Goroutine 4: ID: 5, Name: 孙七, Email: sunqi@fgedu.net.cn
Goroutine 5: ID: 6, Name: 周八, Email: zhouba@fgedu.net.cn
Goroutine 6: ID: 7, Name: 钱十更新, Email: qianshi_updated@fgedu.net.cn
Goroutine 7: ID: 1, Name: 张三事务更新, Email: zhangsan@fgedu.net.cn
Goroutine 8: ID: 2, Name: 李四事务更新, Email: lisi@fgedu.net.cn
Goroutine 9: ID: 3, Name: 王五, Email: wangwu@fgedu.net.cn
Goroutine 10: ID: 4, Name: 赵六, Email: zhaoliu@fgedu.net.cn
Goroutine 11: ID: 5, Name: 孙七, Email: sunqi@fgedu.net.cn
Goroutine 12: ID: 6, Name: 周八, Email: zhouba@fgedu.net.cn
Goroutine 13: ID: 7, Name: 钱十更新, Email: qianshi_updated@fgedu.net.cn
Goroutine 14: ID: 1, Name: 张三事务更新, Email: zhangsan@fgedu.net.cn
Goroutine 15: ID: 2, Name: 李四事务更新, Email: lisi@fgedu.net.cn
Goroutine 16: ID: 3, Name: 王五, Email: wangwu@fgedu.net.cn
Goroutine 17: ID: 4, Name: 赵六, Email: zhaoliu@fgedu.net.cn
Goroutine 18: ID: 5, Name: 孙七, Email: sunqi@fgedu.net.cn
Goroutine 19: ID: 6, Name: 周八, Email: zhouba@fgedu.net.cn
测试完成
4.3 批量操作示例
4.3.1 批量导入数据
import (
“database/sql”
“fmt”
_ “github.com/lib/pq”
)
func main() {
// 连接数据库
connStr := “postgres://fgedu:fgedu123@fgedu.localhost:5432/fgedudb?sslmode=disable”
db, err := sql.Open(“postgres”, connStr)
if err != nil {
fmt.Println(“连接失败:”, err)
return
}
defer db.Close()
// 开始事务
tx, err := db.Begin()
if err != nil {
fmt.Println(“开始事务失败:”, err)
return
}
// 准备语句
stmt, err := tx.Prepare(“INSERT INTO fgedu_users (name, email) VALUES ($1, $2)”)
if err != nil {
tx.Rollback()
fmt.Println(“准备语句失败:”, err)
return
}
defer stmt.Close()
// 批量插入1000条数据
count := 0
for i := 1; i <= 1000; i++ {
_, err := stmt.Exec(fmt.Sprintf("用户%d", i), fmt.Sprintf("user%d@fgedu.net.cn", i))
if err != nil {
tx.Rollback()
fmt.Println("插入失败:", err)
return
}
count++
// 每100条打印一次
if i%100 == 0 {
fmt.Printf("已插入 %d 条数据\n", i)
}
}
// 提交事务
err = tx.Commit()
if err != nil {
fmt.Println("提交事务失败:", err)
return
}
fmt.Printf("批量导入完成,共插入 %d 条数据\n", count)
}
// 输出示例
已插入 100 条数据
已插入 200 条数据
已插入 300 条数据
已插入 400 条数据
已插入 500 条数据
已插入 600 条数据
已插入 700 条数据
已插入 800 条数据
已插入 900 条数据
已插入 1000 条数据
批量导入完成,共插入 1000 条数据
Part05-风哥经验总结与分享
5.1 pq库最佳实践
pq库最佳实践:
- 使用连接池:在生产环境中,使用连接池管理数据库连接
- 参数化查询:始终使用参数化查询,防止SQL注入
- 事务管理:合理使用事务,确保数据一致性
- 错误处理:实现完善的错误处理机制
- 资源管理:使用defer语句确保资源正确释放
- 批量操作:对于大量数据操作,使用批量插入和更新
5.2 常见问题与解决方案
常见问题及解决方案:
# 解决方法:使用defer语句确保连接正确关闭
# 问题2:SQL注入
# 解决方法:使用参数化查询,避免直接拼接SQL语句
# 问题3:性能问题
# 解决方法:使用连接池、批量操作、预编译语句等
# 问题4:事务管理问题
# 解决方法:合理使用事务,确保事务的原子性、一致性、隔离性和持久性
# 问题5:类型转换问题
# 解决方法:了解Go类型和PostgreSQL类型之间的映射关系
5.3 性能优化技巧
性能优化技巧:
- 使用连接池:减少连接建立和关闭的开销
- 批量操作:减少网络往返次数
- 预编译语句:提高查询执行效率
- 并发处理:利用Go的并发特性提高性能
- 合理设置连接池参数:根据实际需求调整连接池大小
- 使用索引:在查询条件列上创建索引
本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html
