mysql数据对比工具
导读:笔者最近在采用 trino 代替旧有方案进行媒体接口数据拉取。需通过将 trino 拉取的数据入到测试库,并与旧方案拉取到生产库中的数据进行对比从而验证逻辑准确性。在进行数据对比时为提高效率因此...
2024.11.21go 原生提供了对数据库的支持,就是 database/sql 包,对关系型的数据库进行了通用的抽象,轻量、面向行的接口,所以使用这个包还需要下载对相应的数据库驱动,比如 mysql 的驱动包 github.com/go-sql-driver/mysql,执行:
go get -u github.com/go-sql-driver/mysql由于不需要调用驱动包的含糊,只需要其执行一次 init 函数,imoprt 部分往往是:
"database/sql"_ "github.com/go-sql-driver/mysql"不引入驱动,调用 sql 包的函数的时候会收到提示:
sql: unknown driver "mysql" (forgotten import?)为什么引入 mysql 的包后就可以工作了呢? 这是因为 mysql 包里的 init 函数里执行了 sql.Register 函数:
func init() {sql.Register("mysql", &MySQLDriver{})}查看 Register 函数的实现,会发现内部把驱动类型和驱动实例做了一个映射保存在 drivers 中,对 MySQLDriver 来说,就是 "mysql" => &MySQLDriver{},Register 函数的第二个参数是 driver.Driver 接口对象,它只要求实现 1 个方法:
type Driver interface { Open(name string) (Conn, error)}Open 函数返回一个 driver.Conn 对象,根据注释得知该 Conn 对象在同一时间只会被一个 goroutine 所占用,通过查看 MySQLDriver 的 Open 函数发现它使用了内部的 connector 对象来实现,而 connector 对象又将功能更委托给了 mysqlConn 对象,该连接对象负责和 mysql 之间交互的协议。
sql 包使用 sql.Open 来获得一个数据库操作对象 sql.DB,而这个 DB 结构体中最重要的就是 driver.Connector,它要求实现 2 个方法:
1. Connect(context.Context) (Conn, error)
2. Driver() Driver
func getMysqlDB() *sql.DB { db, _ := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?charset=utf8&parseTime=true") //db, _ := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True") //db.Ping() return db}问题是这个 sql.Open 里的 connector 是怎么和 mysql 驱动的 connector 连接上的呢? F12 进入 sql.Open 函数,发现它会尝试从 dirvers 获取 MySQLDriver 的实例,尝试转换成 driver.DriverContext 后调用其 OpenConnector 函数,如果转换失败了也会构造一个内部的 dsnconnector 对象,调用 driver 上的 Open 方法。
而 MySQLDriver 对 Open 和 OpenConnector 都提供了实现:
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {cfg, err := ParseDSN(dsn)if err != nil {return nil, err}c := &connector{cfg: cfg,}return c.Connect(context.Background())}func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error) {cfg, err := ParseDSN(dsn)if err != nil {return nil, err}return &connector{cfg: cfg,}, nil}sql 包依靠 Open、Register 2 个函数实现了驱动包的注入,获得 driver.Conn 对象后,主要工作就是解析 sql 查询和获得结果了(mysql 交互协议),因为查询通常分为 2 种类型:
1. query 查询带结果,比如 select 等
2. exec 只需执行,比如 update、delete 等
具体 sql 包在调用时会尝试把 driver.Conn 对象转换成 Queryer、QueryerContext 和 Execer、ExecerContext 执行其对应的方法,不过这一切使用 sql.DB 做为中间对象来操作,在 DB 内部它对 driver.Conn 对象做了进一层带锁的包装 driverConn,在任何查询前都会使用 db.conn 内部方法里获取 driverConn,它加锁以后从 freeConn 里取出一个并标记为 inUse 在使用,否则就检测是否达到最大连接数,没有就就调用 connector 对象的 Connect 方法获得 driver.Conn 后构造一个新的 driverConn,用完了后都会调用它的 relaseConn 把连接对象重新 put 回 freeConn 列表里。至此,go sql 驱动包的加载和执行的流程都清楚了。
mysql 的 go hello 版本如下:
func main() { var str string db := getMysqlDB() row := db.QueryRow("select ‘hello go-mysql‘") if row.Scan(&str) == nil {fmt.Println(str) }}批量查询和使用预编译的查询:
func scan() {db := session.GetMysqlDB()rows, _ := db.Query("select * FROM user")defer rows.Close()// 遍历 rows}func single() {var user Userdb := session.GetMysqlDB()db.QueryRow("select * FROM user WHERE id = 1").Scan(&user.id, &user.passport, &user.password, &user.nickname, &user.createdAt)fmt.Println(user)}func prepare() {db := session.GetMysqlDB()stmt, _ := db.Prepare("select * FROM user WHERE id = ?")defer stmt.Close()rows, _ := stmt.Query(1)defer rows.Close()//}怎么遍历 rows 呢? 需要使用 Next() 来判断,当遇到 io.EOF 或者 rows 被 close 会结束遍历。
for rows.Next() { var user User rows.Scan(&user.id, &user.passport, &user.password, &user.nickname, &user.createdAt) fmt.Println(user)}// 使用 rows.Error 来得到错误插入时使用 LastInsertId 来获取最后一行的 id:
func main() {db := session.GetMysqlDB()stmt, _ := db.Prepare("insert INTO user(`passport`, `password`,`nickname`) VALUES(?, ?, ?)")result, _ := stmt.exec("lisi", "abc123", "李四")fmt.Println(result.LastInsertId())}使用 begin 和 beginTx 开启一个事务,begin 使用默认的 context.Background() 来调用 beginTx,事务会独占数据库连接,但是 Tx 对象上的方法和 sql.DB 都是一一对应,调用 commit 或者 rollback 后,Tx 对象才被 Close 掉。
func main() {db := session.GetMysqlDB()stmt, _ := db.Prepare("insert INTO user(`passport`, `password`,`nickname`) VALUES(?, ?, ?)")result, _ := stmt.exec("lisi", "abc123", "李四")fmt.Println(result.LastInsertId())}NULL 字段有时候会很麻烦,处理 NULL 有两个办法:
1. 使用数据库函数 COALESCE 让可能 NULL 的字段不可能返回 NULL
2. 使用 sql.NullString、sql.NullInt32 等类型
type NullString struct { String string Validbool // Valid is true if String is not NULL}对于第二种办法,Scan 操作后需要先用 Valid 判断一下,为 true 则调用 String 或者 Value 方法,值得注意的并没有 sql.NullUint32 等类型,可以自定义 NULL 类型,实现 Scan 和 Value 方法。比如 NullInt32 的实现如下:
// NullInt32 represents an int32 that may be null.// NullInt32 implements the Scanner interface so// it can be used as a scan destination, similar to NullString.type NullInt32 struct { Int32 int32 Valid bool // Valid is true if Int32 is not NULL}// Scan implements the Scanner interface.func (n *NullInt32) Scan(value interface{}) error { if value == nil { n.Int32, n.Valid = 0, false return nil } n.Valid = true return convertAssign(&n.Int32, value)}// Value implements the driver Valuer interface.func (n NullInt32) Value() (driver.Value, error) { if !n.Valid { return nil, nil } return int64(n.Int32), nil}如你所见,上面的 Scan 的用法其实非常的不方便,实际项目里用 struct tag + orm 来做比较多,下面是使用 sqlx (go get https://github.com/jmoiron/sqlx) 的例子:
type User struct {IDint`db:"id"`Passportstring`db:"passport"`Passwordstring`db:"password"`Nicknamestring`db:"nickname"`CreatedAt time.Time `db:"create_time"`}func main() {var total intvar user Uservar users []Uservar names []stringdb := session.GetSqlxDB()db.Get(&total, "select COUNT(*) FROM user")fmt.Println(total)db.Get(&user, "select * FROM user LIMIT 1")fmt.Println(user)db.select(&users, "select * FROM user")fmt.Println(users)db.select(&names, "select nickname FROM user")fmt.Println(names)db.QueryRowx("select * FROM user LIMIT 1").StructScan(&user)fmt.Println(user)rows, _ := db.Queryx("select * FROM user")defer rows.Close()for rows.Next() {var u Userrows.StructScan(&u)fmt.Println(u)}}除了好用的 Get、select、StructScan,还有 MapScan、SliceScan 分别对应 map[string]interface{} 和 []interface{},如果需要数据库 ORM 可以关注下 https://github.com/go-gorm/gorm。
本章节的代码 https://github.com/developdeveloper/go-demo/tree/master/22-access-rdb-sql
导读:笔者最近在采用 trino 代替旧有方案进行媒体接口数据拉取。需通过将 trino 拉取的数据入到测试库,并与旧方案拉取到生产库中的数据进行对比从而验证逻辑准确性。在进行数据对比时为提高效率因此...
2024.11.21前一篇文章《windows服务器应用系统自动备份策略》讲到了mysql数据库本地备份策略,如果觉得备份到服务器上不够安全,防止服务器意外硬件损坏致使备份也丢失。那么可以采取异地备份,网上很多windo...
2024.11.19平常在迁移和测试系统的时候,经常需要迁移数据库,数据量不大的时候,我们可以用mysql dump导出sql,然后再导入,但是假如里面有的表,数据量特别大,比如logs表,几百万,用mysql_dump...
2024.11.19上午刚工作10分左右,同事说在使用jira时出现问题,具体截图如下:通过上图的报错信息:定位为mysql数据库连接数的问题解决方法:1.登录mysql进行查看Mysql –uroot –p123456...
2024.11.211.jdbc的基本概念java database connectiveity:Java数据库连接说人话就是:java语言操作数据库程序员 操作 Java语言此时程序员就希望使用一套统一的java代码这...
2024.11.08