Go语言搬砖 操作mysql

1,140 阅读4分钟

这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战

简介

关系型数据库mysql流行度非常高,且已经流行了很多年了,在DB-Engines排行榜目前依然是稳稳的第二,虽然较去年有下降的趋势,但依然炙手可热。

排行榜传送门: db-engines.com/en/ranking

image.png

安装

使用docker快速安装,往期传送门: juejin.cn/post/684490…

安装mysql5.7版本,并设置root密码为6个6

docker run --name mysql -e MYSQL_ROOT_PASSWORD=666666 -d -p 3306:3306 mysql:5.7

UI工具博主使用的是款免费的轻量级工具HeidiSQL: www.heidisql.com/

---也下图片来源于HeidiSQL官网 image.png

喜欢用VScode的兄弟,也可以直接在VScode安装基于mysql插件,在VScode写sql

客户端

go官方包中没有提供数据库驱动,只是为开发数据库定义了一些标准接口

我们要操作mysql数据库需要使用第三方包。但是了第三方包众多,最火的是: github.com/go-sql-driv…

还有一个基于官方的 database/sql的扩展包,也非常火,传送门: github.com/jmoiron/sql…

在本文中有可能 会交叉使用以上两种包,也可能只使用一种,不做保证

当然一些著名的ORM框架(GORM,XORM,gorose)这里不多赘述

api例子

初始化

将客户端定义为全局变局,方便后续使用

定义一组mysql配置变量,然后初始化连接

在初始化连接之前 需要创建好test这个库

var (
   UserName  string = "root"
   PassWord  string = "666666"
   IP string = "ip"
   Port int = 3306
   DbName string = "test"
   CharSet string = "utf8mb4"
   
   Db *sql.DB
   err error
)

func init() {
   dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", UserName, PassWord, IP, Port, DbName, CharSet)
   Db, err = sql.Open("mysql", dsn)
   if err != nil {
      log.Fatalln("连接mysql失败: ",err)
   }
}

创建表

创建一个用户信息表,主键为id,且自增加1

func createTable() {
   sql := `
    create table user_info(
           id bigint unsigned not null auto_increment,
           name varchar(100) NOT NULL DEFAULT '' COMMENT '用户名',
           email varchar(50) NOT NULL DEFAULT '' COMMENT '用户邮箱', 
           age  int(11) NOT NULL DEFAULT '0' COMMENT '用户年龄', 
           primary key (id)
    ) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4;`
   result, err := Db.Exec(sql)
   if err != nil {
      log.Println("创建表失败: ",err)
   }
   log.Println(result.RowsAffected())
   log.Println(result.LastInsertId())
}

插入数据

写个小循环,写入100条数据

func insertData() {
   for i:=1;i<100 ;i++  {
      result, _ := Db.Exec("insert into user_info (name,age) value (?,?)", "zhangsan" + strconv.Itoa(i),18+i)
      log.Println(result.LastInsertId())
   }
}

查询数据

事先定义好结构体

指定id,查询一条数据

调用QueryRow方法,根据查询条件返回一个结果

type UserInfo struct {
   Id  int
   Name string
   email string
   age int
}

func selectData() {
   var u UserInfo
   sqlStr := "select id,name,age from user_info where id=?"
   _ = Db.QueryRow(sqlStr, 2).Scan(&u.Id, &u.Name, &u.age)
   fmt.Println(u.Id,u.Name,u.age)
}

查询多条数据

调用Query方法,返回一个结果集,然后循环遍历输出

sqlStr2 := "select id,name,age from user_info where id > ?"
rows, _ := Db.Query(sqlStr2, 0)
//函数结束关闭连接
defer rows.Close()

for rows.Next(){
   var u UserInfo
   _ = rows.Scan(&u.Id, &u.Name, &u.age)
   fmt.Println(u.Id,u.Name,u.age)
}

更新数据

更新ID1为的数据,将age字段值改为18

func updateData() {
   sqlStr := "update user_info set age=? where id = ?"
   exec, _ := Db.Exec(sqlStr, 18, 1)
   fmt.Println(exec.RowsAffected())
}

删除数据

删除id为5的数据,因为ID为自增,所有应该只删除了一条

可以从RowsAffected方法返回看出影响了多少条数据

func deleteData() {
   sqlStr := "delete user_info user where id = ?"
   exec, _ := Db.Exec(sqlStr, 5)
   fmt.Println(exec.RowsAffected())
}

查看类型

查看表 数据库和GO语言类型

func columnType() {
   sqlStr := "select * from user_info"
   query, _ := Db.Query(sqlStr)
   columns, _ := query.ColumnTypes()
   for _,column := range columns {
      fmt.Printf("字段名: %v, 数据库类型: %v,GO语言映射类型: %v\n",column.Name(),column.DatabaseTypeName(),column.ScanType())
   }
}

方法总结

  • db.Query 执行查询,返回有结果的值,通常是select
  • db.Exce 执行命令,不返回结果的值,通常是更新,删除等
  • rows.Next() 迭代查询数据
  • rows.Scan() 读取每一行的值
  • QueryRow 查询并返回一行数据

Mysql事务

以下理论来源于网络

事务是一个最小的不可再分的工作单元,通常一个事务对应一个完整的业务

经典的事务案例都拿银行转账来举例,其特性是要么成功,要么失败,没有其它状态

mysql中数据库引擎常用的是InnoDB,因其支持事务

事务的ACID

  • 原子性 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样

  • 一致性 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作

  • 隔离性 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)

  • 持久性 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失

其中隔离级别有多种,见下图 ----图片来源于网络

image.png

事务例子

该例子做二次事务的操作,一次成功完成,一次回滚

func transaction() {
   //清空user_info表数据
   sqlStr := "truncate table user_info;"
   Db.Exec(sqlStr)

   sqlStr2 := "insert into user_info(name,age) values (?,?)"
   tx1, _ := Db.Begin()
   result, _ := tx1.Exec(sqlStr2, "zhangsan17", 17)
   fmt.Println(result.RowsAffected())
   fmt.Println(result.LastInsertId())
   tx1.Commit()

   tx2, _ := Db.Begin()
   result2, _ := tx2.Exec(sqlStr2, "zhangsan16", 16)
   fmt.Println(result2.RowsAffected())
   fmt.Println(result2.LastInsertId())
   tx2.Rollback()

   sqlStr3 := "select id,name,age from user_info"
   result3, _ := Db.Query(sqlStr3)
   for result3.Next(){
      var u UserInfo
      _ = result3.Scan(&u.Id, &u.Name, &u.age)
      fmt.Println(u.Id,u.Name,u.age)
   }
}

总结

go-sql-driver/mysql 其实也是基于官方的database/sql的增强包,例子在大部分sql语句都是手拼的,如果使用orm包的话,基本都是方法链式操作

对于微型小项目,直接使用该包体积小,交互速度也比较快