前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang源码分析(34)mysql驱动

golang源码分析(34)mysql驱动

作者头像
golangLeetcode
发布2022-08-02 17:11:16
9850
发布2022-08-02 17:11:16
举报
文章被收录于专栏:golang算法架构leetcode技术php
  • Mysql库https://github.com/go-sql-driver/mysql
  • Go本身不提供具体数据库驱动,只提供驱动接口和管理。
  • 各个数据库驱动需要第三方实现,并且注册到Go中的驱动管理中。

安装golang mysql drvier

go get github.com/go-sql-driver/mysql

代码中需要注册mysql数据库驱动,通过引入空白导入mysql包来完成。

为什么需要使用空白导入?是因为需要执行mysql包的初始化代码(代码位于%GOPATH%/github.com/go-sql-driver/mysql/driver.go)

代码语言:javascript
复制
func init() {
    sql.Register("mysql", &MySQLDriver{})
}

连接数据的DSN格式

username:password@protocol(address)/dbname?param=value

Prepared Statement sql.Stmt支持预备表达式,可以用来优化SQL查询提高性能,减少SQL注入的风险, DB.Prepare()和Tx.Prepare()都提供了对于预备表达式的支持。

预处理的流程: step1. 将sql分为2部分.命令部分和数据部分. step2. 首先将命令部分发送给mysql服务器,mysql进行预处理.(如生成AST) step3. 然后将数据部分发送给mysql服务器,mysql进行占位符替换. step4. mysql服务器执行sql语句,把执行结果发送给客户端.

预处理的优势: 1.因为发送命令后,在mysql服务器端,就会将AST生成好,所以不需要对每一次值的更换都重新生成一次AST.对同样的数据不同的SQL来讲,只需生成1次AST,并缓存起来即可. 2.避免SQL注入.因为mysql知道再次发送过来的内容为”数据”,因此不会将这些数据解析为SQL,避免了SQL注入.

需要注意的点: 使用预处理进行查询操作时,不仅在defer时需要关闭结果集,而且还要关闭命令句柄,否则同样会占用连接,导致阻塞.

代码语言:javascript
复制
package main

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "fmt"
)

type User struct {
    Id int `db:"id"`
    Name string `db:"name"`
    Age int `db:"age"`
}


func PrepareQuery(db *sql.DB, id int) {
    stmt, err := db.Prepare("select id, name, age from user where id>?")
    if err != nil {
        panic(err)
    }

    rows, err := stmt.Query(id)
    if err != nil {
        panic(err)
    }

    defer stmt.Close()
    defer rows.Close()

    for rows.Next(){
        var user User
        err := rows.Scan(&user.Id, &user.Name, &user.Age)
        if err != nil {
            panic(err)
        }
        fmt.Printf("user: %#v\n", user)
    }
}

func main() {

    dns := "root:123456@tcp(172.16.65.200:3306)/golang"
    db, err := sql.Open("mysql", dns)
    if err != nil {
        panic(err)
    }

    defer db.Close()

    PrepareQuery(db, 0)

}

Mysql创建表:

  CREATE TABLE user (

  id int(20) NOT NULL AUTO_INCREMENT,

  name varchar(20) DEFAULT '',

  age int(2) DEFAULT '0',

  PRIMARY KEY (id))

  ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

数据库增删改查

insert

代码语言:javascript
复制
package main

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "fmt"
)

func Insert(db *sql.DB) {
    name := "Vincent"
    age := 18

    result, err := db.Exec("insert into user(name, age) values (?,?)", name, age)
    if err != nil {
        panic(err)
    }

    id, err := result.LastInsertId()
    if err != nil {
        panic(err)
    }

    affected, err := result.RowsAffected()
    if err != nil {
        panic(err)
    }

    fmt.Printf("last insert id:%d affect rows:%d\n", id, affected)
}

func main() {
    dns := "root:123456@tcp(172.16.65.200:3306)/golang"
    db, err := sql.Open("mysql", dns)
    if err != nil {
        panic(err)
    }

    err = db.Ping()
    if err != nil {
        panic(err)
    }
    fmt.Println("connect to db success!!!")
    Insert(db)
}

delete

代码语言:javascript
复制
package main

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "fmt"
)

func Delete(db *sql.DB, id int) {
    result, err := db.Exec("delete from user where id=?", id)
    if err != nil {
        panic(err)
    }

    rowsAffected, err := result.RowsAffected()
    if err != nil {
        panic(err)
    }
    fmt.Printf("delect id:%d, affect rows:%d\n", id, rowsAffected)
}

func main() {
    dns := "root:123456@tcp(172.16.65.200:3306)/golang"
    db, _ := sql.Open("mysql", dns)
    Delete(db, 2)
}

update

代码语言:javascript
复制
package main

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "fmt"
)

func Update(db *sql.DB) {
    name := "Miles"
    age := 88
    id := 3

    result, err := db.Exec("update user set name=?, age=? where id=?", name, age, id)
    if err != nil {
        panic(err)
    }

    // RowsAffected returns the number of rows affected by an
    // update, insert, or delete.
    rowsAffected, err := result.RowsAffected()
    if err != nil {
        panic(err)
    }

    fmt.Printf("update id:%d, affect rows:%d\n", id, rowsAffected)

}

func main() {
    dns := "root:123456@tcp(172.16.65.200:3306)/golang"
    db, err := sql.Open("mysql", dns)
    if err != nil {
        panic(err)
    }

    err = db.Ping()
    if err != nil {
        panic(err)
    }
    fmt.Println("connect to db success!!!")
    Update(db)
}

query

代码语言:javascript
复制
package main

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

type User struct {
    Id int `db:"id"`
    Name string `db:"name"`
    Age int `db:"age"`
}



// 单行查询,如果查询到多个结果,只返回第一行,查询不到结果就ErrNoRows错误。
func QueryRow(db *sql.DB) {
    id:= 2
    row := db.QueryRow("select id, name, age from user where id=?", id)

    var user User
    err := row.Scan(&user.Id, &user.Name, &user.Age)

    if err == sql.ErrNoRows {
        log.Printf("not found data of the id:%d",id)
    }

    if err != nil {
        panic(err)
    }


    fmt.Printf("user: %#v\n", user)
}


// 多行查询, 查询不到任何记录也不会报错。
func Query(db *sql.DB) {
    id := 0
    rows, err := db.Query("select id, name, age from user where id>?", id)
    if err != nil {
        panic(err)
    }
    if err == sql.ErrNoRows {
        log.Printf("not found data of id:%d\n", id)
        return
    }
    defer rows.Close()

    for rows.Next() {
        var user User
        err := rows.Scan(&user.Id, &user.Name, &user.Age)
        if err != nil {
            panic(err)
        }
        fmt.Printf("user: %#v\n", user)
    }

}

func main() {
    dns := "root:123456@tcp(172.16.65.200:3306)/golang"
    db, err := sql.Open("mysql", dns)
    if err != nil {
        panic(err)
    }

    err = db.Ping()
    if err != nil {
        panic(err)
    }

    fmt.Printf("connect to db success\n")

    //QueryRow(db)

    Query(db)
}

事务支持

事务(transaction)

  • transaction, err := Db.Begin() 开启事务
  • transaction.Exec() 执行事务
  • transaction.Commit() 提交事务
  • transaction.Rollback() 回滚事务

A. 事务的应用场景   1. 同时更新多个表   2. 同时更新多行数据 B. 事务的ACID   1. 原子性   2. 一致性   3. 隔离性   4. 持久性

需要注意的点: 1. 执行失败要回滚 2. 提交失败要回滚

代码语言:javascript
复制
package main

import (
    _ "github.com/go-sql-driver/mysql"
    "database/sql"
    "fmt"
)


func Transaction(db *sql.DB) {

    // 开启事务
    tx, err := db.Begin()

    if err != nil {
        panic(err)
    }

    result, err := tx.Exec("insert into user(name, age)values(?,?)", "Jack", 98)
    if err != nil {
        // 失败回滚
        tx.Rollback()
        panic(err)
    }
    
    fmt.Println("result", result)

    exec, err := tx.Exec("update user set name=?, age=? where id=?", "Jack", 98, 1)
    if err != nil {
        // 失败回滚
        tx.Rollback()
        panic(err)
    }
    fmt.Println("exec", exec)

    // 提交事务
    err = tx.Commit()
    
    if err != nil {
        // 失败回滚
        tx.Rollback()
        panic(err)
    }
}

func main() {

    dns := "root:123456@tcp(172.16.65.200:3306)/golang"
    db, err := sql.Open("mysql", dns)
    if err != nil {
        panic(err)
    }

    err = db.Ping()
    if err != nil {
        panic(err)
    }

    Transaction(db)
}

Mysql日期时间类型报错

代码语言:javascript
复制
sql: Scan error on column index 1: unsupported Scan, storing driver.Value type []uint8 into type *time.Time

原因是在调用sql.Open()时没有将parseTime设置为True。加入parseTime即可修复问题:

代码语言:javascript
复制
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname?parseTime=true")
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-11-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Mysql日期时间类型报错
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档