golang操作mysql使用总结

本文详细介绍如何使用Golang的database/sql包操作MySQL数据库,包括数据库连接、查询、插入数据及预编译语句的使用。

golang操作mysql使用总结

前言

Golang 提供了database/sql包用于对SQL数据库的访问, 作为操作数据库的入口对象sql.DB, 主要为我们提供了两个重要的功能:

  • sql.DB 通过数据库驱动为我们提供管理底层数据库连接的打开和关闭操作.
  • sql.DB 为我们管理数据库连接池

需要注意的是,sql.DB表示操作数据库的抽象访问接口,而非一个数据库连接对象;它可以根据driver打开关闭数据库连接,管理连接池。正在使用的连接被标记为繁忙,用完后回到连接池等待下次使用。所以,如果你没有把连接释放回连接池,会导致过多连接使系统资源耗尽。

操作mysql

1.导入mysql数据库驱动

1
2
3
4
import (
   "database/sql"
   _ "github.com/go-sql-driver/mysql"
)

 

通常来说, 不应该直接使用驱动所提供的方法, 而是应该使用 sql.DB, 因此在导入 mysql 驱动时, 这里使用了匿名导入的方式(在包路径前添加 _), 当导入了一个数据库驱动后, 此驱动会自行初始化并注册自己到Golang的database/sql上下文中, 因此我们就可以通过 database/sql 包提供的方法访问数据库了.

2.连接数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type DbWorker struct {
    //mysql data source name
    Dsn string 
}

func main() {
    dbw := DbWorker{
        Dsn: "user:password@tcp(127.0.0.1:3306)/test",
    }	
    db, err := sql.Open("mysql",
        dbw.Dsn)
    if err != nil {
        panic(err)
        return
    }
    defer db.Close()
}

 

通过调用sql.Open函数返回一个sql.DB指针; sql.Open函数原型如下:

1
func Open(driverName, dataSourceName string) (*DB, error)

 

  • driverName: 使用的驱动名. 这个名字其实就是数据库驱动注册到 database/sql 时所使用的名字.
  • dataSourceName: 数据库连接信息,这个连接包含了数据库的用户名, 密码, 数据库主机以及需要连接的数据库名等信息.
  1. sql.Open并不会立即建立一个数据库的网络连接, 也不会对数据库链接参数的合法性做检验, 它仅仅是初始化一个sql.DB对象. 当真正进行第一次数据库查询操作时, 此时才会真正建立网络连接;
  2. sql.DB表示操作数据库的抽象接口的对象,但不是所谓的数据库连接对象,sql.DB对象只有当需要使用时才会创建连接,如果想立即验证连接,需要用Ping()方法;
  3. sql.Open返回的sql.DB对象是协程并发安全的.
  4. sql.DB的设计就是用来作为长连接使用的。不要频繁Open, Close。比较好的做法是,为每个不同的datastore建一个DB对象,保持这些对象Open。如果需要短连接,那么把DB作为参数传入function,而不要在function中Open, Close。

3.数据库基本操作

数据库查询的一般步骤如下:

  1. 调用 db.Query 执行 SQL 语句, 此方法会返回一个 Rows 作为查询的结果
  2. 通过 rows.Next() 迭代查询数据.
  3. 通过 rows.Scan() 读取每一行的值
  4. 调用 db.Close() 关闭查询

现有user数据库表如下:

1
2
3
4
5
6
CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT '',
  `age` int(11) DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4

 

MySQL 5.5 之前, UTF8 编码只支持1-3个字节,从MYSQL5.5开始,可支持4个字节UTF编码utf8mb4,一个字符最多能有4字节,utf8mb4兼容utf8,所以能支持更多的字符集;关于emoji表情的话mysql的utf8是不支持,需要修改设置为utf8mb4,才能支持。

查询数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func (dbw *DbWorker) QueryData() {
	dbw.QueryDataPre()
	rows, err := dbw.Db.Query(`SELECT * From user where age >= 20 AND age < 30`)
	defer rows.Close()
	if err != nil {
		fmt.Printf("insert data error: %v\n", err)
		return
	}
	for rows.Next() {
		rows.Scan(&dbw.UserInfo.Id, &dbw.UserInfo.Name, &dbw.UserInfo.Age)
		if err != nil {
			fmt.Printf(err.Error())
			continue
		}
		if !dbw.UserInfo.Name.Valid {
			dbw.UserInfo.Name.String = ""
		}
		if !dbw.UserInfo.Age.Valid {
			dbw.UserInfo.Age.Int64 = 0
		}
		fmt.Println("get data, id: ", dbw.UserInfo.Id, " name: ", dbw.UserInfo.Name.String, " age: ", int(dbw.UserInfo.Age.Int64))
	}

	err = rows.Err()
	if err != nil {
		fmt.Printf(err.Error())
	}
}

 

  1. rows.Scan 参数的顺序很重要, 需要和查询的结果的column对应. 例如 “SELECT * From user where age >=20 AND age < 30” 查询的行的 column 顺序是 “id, name, age” 和插入操作顺序相同, 因此 rows.Scan 也需要按照此顺序 rows.Scan(&id, &name, &age), 不然会造成数据读取的错位.
  2. 因为golang是强类型语言,所以查询数据时先定义数据类型,但是查询数据库中的数据存在三种可能:存在值,存在零值,未赋值NULL 三种状态, 因为可以将待查询的数据类型定义为sql.Nullxxx类型,可以通过判断Valid值来判断查询到的值是否为赋值状态还是未赋值NULL状态.
  3. 每次db.Query操作后, 都建议调用rows.Close(). 因为 db.Query() 会从数据库连接池中获取一个连接, 这个底层连接在结果集(rows)未关闭前会被标记为处于繁忙状态。当遍历读到最后一条记录时,会发生一个内部EOF错误,自动调用rows.Close(),但如果提前退出循环,rows不会关闭,连接不会回到连接池中,连接也不会关闭, 则此连接会一直被占用. 因此通常我们使用 defer rows.Close() 来确保数据库连接可以正确放回到连接池中; 不过阅读源码发现rows.Close()操作是幂等操作,即一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同, 所以即便对已关闭的rows再执行close()也没关系.

单行查询

1
2
3
4
5
6
var name string
err = db.QueryRow("select name from user where id = ?", 1).Scan(&name)
if err != nil {
    log.Fatal(err)
}
fmt.Println(name)

 

  1. err在Scan后才产生,上述链式写法是对的
  2. 需要注意Scan()中变量和顺序要和前面Query语句中的顺序一致,否则查出的数据会映射不一致.

插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
func (dbw *DbWorker) insertData() {
	ret, err := dbw.Db.Exec(`INSERT INTO user (name, age) VALUES ("xys", 23)`)
	if err != nil {
		fmt.Printf("insert data error: %v\n", err)
		return
	}
	if LastInsertId, err := ret.LastInsertId(); nil == err {
		fmt.Println("LastInsertId:", LastInsertId)
	}
	if RowsAffected, err := ret.RowsAffected(); nil == err {
		fmt.Println("RowsAffected:", RowsAffected)
	}
}

 

通过db.Exec()插入数据,通过返回的err可知插入失败的原因,通过返回的ret可以进一步查询本次插入数据影响的行数RowsAffected和最后插入的Id(如果数据库支持查询最后插入Id).

github完整代码示例

4.预编译语句(Prepared Statement)
预编译语句(PreparedStatement)提供了诸多好处, 因此我们在开发中尽量使用它. 下面列出了使用预编译语句所提供的功能:

  • PreparedStatement 可以实现自定义参数的查询
  • PreparedStatement 通常来说, 比手动拼接字符串 SQL 语句高效.
  • PreparedStatement 可以防止SQL注入攻击

一般用Prepared StatementsExec()完成INSERTUPDATEDELETE操作。

下面是将上述案例用Prepared Statement 修改之后的完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package main

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

type DbWorker struct {
	Dsn      string
	Db       *sql.DB
	UserInfo userTB
}
type userTB struct {
	Id   int
	Name sql.NullString
	Age  sql.NullInt64
}

func main() {
	var err error
	dbw := DbWorker{
		Dsn: "root:123456@tcp(localhost:3306)/sqlx_db?charset=utf8mb4",
	}
	dbw.Db, err = sql.Open("mysql", dbw.Dsn)
	if err != nil {
		panic(err)
		return
	}
	defer dbw.Db.Close()

	dbw.insertData()
	dbw.queryData()
}

func (dbw *DbWorker) insertData() {
	stmt, _ := dbw.Db.Prepare(`INSERT INTO user (name, age) VALUES (?, ?)`)
	defer stmt.Close()

	ret, err := stmt.Exec("xys", 23)
	if err != nil {
		fmt.Printf("insert data error: %v\n", err)
		return
	}
	if LastInsertId, err := ret.LastInsertId(); nil == err {
		fmt.Println("LastInsertId:", LastInsertId)
	}
	if RowsAffected, err := ret.RowsAffected(); nil == err {
		fmt.Println("RowsAffected:", RowsAffected)
	}
}

func (dbw *DbWorker) QueryDataPre() {
	dbw.UserInfo = userTB{}
}
func (dbw *DbWorker) queryData() {
	stmt, _ := dbw.Db.Prepare(`SELECT * From user where age >= ? AND age < ?`)
	defer stmt.Close()

	dbw.QueryDataPre()

	rows, err := stmt.Query(20, 30)
	defer rows.Close()
	if err != nil {
		fmt.Printf("insert data error: %v\n", err)
		return
	}
	for rows.Next() {
		rows.Scan(&dbw.UserInfo.Id, &dbw.UserInfo.Name, &dbw.UserInfo.Age)
		if err != nil {
			fmt.Printf(err.Error())
			continue
		}
		if !dbw.UserInfo.Name.Valid {
			dbw.UserInfo.Name.String = ""
		}
		if !dbw.UserInfo.Age.Valid {
			dbw.UserInfo.Age.Int64 = 0
		}
		fmt.Println("get data, id: ", dbw.UserInfo.Id, " name: ", dbw.UserInfo.Name.String, " age: ", int(dbw.UserInfo.Age.Int64))
	}

	err = rows.Err()
	if err != nil {
		fmt.Printf(err.Error())
	}
}

 

db.Prepare()返回的statement使用完之后需要手动关闭,即defer stmt.Close()
<think>嗯,用户问的是如何用Go语言把MySQL的数据表转换成SQLite的。首先,我需要理解用户的需求。他们可能是在做数据迁移,或者需要将现有的MySQL数据库适配到SQLite,比如用于嵌入式系统或者移动应用。接下来,我得考虑整个转换过程的步骤。 首先,连接MySQL和SQLite。Go语言有标准的database/sql包,但需要对应的驱动。MySQL的话,常用的驱动是github.com/go-sql-driver/mysql,而SQLite可能需要使用github.com/mattn/go-sqlite3。这两个都需要用go get安装。 然后,需要从MySQL中读取表结构。这里可能需要查询information_schema来获取表的元数据,比如列名、数据类型、是否为主键等。不过需要注意的是,MySQL和SQLite的数据类型并不完全一致,比如MySQL的INT AUTO_INCREMENT在SQLite中对应的是INTEGER PRIMARY KEY AUTOINCREMENT,所以转换的时候要做类型映射。 接下来是处理数据迁移。需要逐行读取MySQL表中的数据,然后插入到SQLite中。这里要考虑事务处理,以提高插入效率,特别是在数据量大的时候。可能还需要处理字符编码的问题,确保数据正确转换。 另外,外键和索引也是需要考虑的部分。MySQL的索引结构可能与SQLite不同,需要重新生成。不过,如果用户只是需要表结构和数据,可能索引可以暂时忽略,或者根据情况处理。 还有一个问题是,如何处理自增主键。在SQLite中,如果使用INTEGER PRIMARY KEY,就会自动自增,所以需要确保在创建表的时候正确定义。 可能还需要考虑错误处理,比如数据类型转换失败,或者某些MySQL特有的功能在SQLite中不支持的情况,这时候需要给出提示或者跳过。 然后,代码结构大概分为几个步骤:连接两个数据库,获取源表结构,生成目标表结构,迁移数据。每个步骤都需要详细的处理,比如读取表结构时,可能需要查询COLUMNS表,获取每个字段的信息,然后根据字段类型映射生成对应的SQLite字段类型。 在数据迁移部分,可能需要分批次读取和插入,避免内存溢出。使用预处理语句可以提高效率,同时使用事务来批量提交,减少IO操作。 测试也是一个重要环节,转换后需要验证数据的一致性和完整性,比如检查行数是否一致,抽样检查数据是否正确。 最后,用户可能需要一个完整的示例代码,这样他们可以快速上手,并根据自己的需求进行修改。在示例代码中,需要处理主要的错误点,比如驱动注册、连接字符串的格式、SQL语句的构建等。 总结一下,整个流程是连接数据库、获取元数据、转换表结构、迁移数据,处理可能的数据类型差异和约束,确保迁移后的SQLite数据库能够正确使用。</think>以下是如何使用GolangMySQL数据表转换为SQLite数据表的详细步骤说明: ### 一、准备工作 1. 安装所需驱动 ```bash go get github.com/go-sql-driver/mysql # MySQL驱动 go get github.com/mattn/go-sqlite3 # SQLite3驱动 ``` 2. 导入必要包 ```go import ( "database/sql" _ "github.com/go-sql-driver/mysql" _ "github.com/mattn/go-sqlite3" "fmt" "log" ) ``` ### 二、连接数据库 ```go // 连接MySQL mysqlDB, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname") if err != nil { log.Fatal(err) } defer mysqlDB.Close() // 创建SQLite数据库 sqliteDB, err := sql.Open("sqlite3", "./test.db") if err != nil { log.Fatal(err) } defer sqliteDB.Close() ``` ### 三、获取MySQL表结构 ```go // 获取所有表名 rows, err := mysqlDB.Query("SHOW TABLES") if err != nil { log.Fatal(err) } defer rows.Close() var tables []string for rows.Next() { var table string if err := rows.Scan(&table); err != nil { log.Fatal(err) } tables = append(tables, table) } ``` ### 四、数据类型映射(关键步骤) | MySQL 数据类型 | SQLite 对应类型 | |---------------------|------------------| | INT | INTEGER | | VARCHAR(n) | TEXT | | DATETIME | TEXT | | DECIMAL(m,n) | REAL | | TEXT | TEXT | | BLOB | BLOB | | BOOLEAN | INTEGER (0/1) | ### 五、表结构转换 ```go for _, table := range tables { // 获取表结构 rows, err := mysqlDB.Query(fmt.Sprintf("SHOW CREATE TABLE %s", table)) if err != nil { log.Fatal(err) } var createSQL string for rows.Next() { var dummy string if err := rows.Scan(&dummy, &createSQL); err != nil { log.Fatal(err) } } rows.Close() // 转换SQL语法 convertedSQL := convertMySQLToSQLite(createSQL) // 在SQLite中创建表 _, err = sqliteDB.Exec(convertedSQL) if err != nil { log.Fatal(err) } } ``` ### 六、数据迁移 ```go // 使用事务提高效率 tx, err := sqliteDB.Begin() if err != nil { log.Fatal(err) } for _, table := range tables { // 读取MySQL数据 rows, err := mysqlDB.Query(fmt.Sprintf("SELECT * FROM %s", table)) if err != nil { log.Fatal(err) } // 获取列信息 columns, err := rows.Columns() if err != nil { log.Fatal(err) } // 构建插入语句 placeholders := make([]string, len(columns)) for i := range placeholders { placeholders[i] = "?" } insertSQL := fmt.Sprintf("INSERT INTO %s VALUES (%s)", table, strings.Join(placeholders, ",")) // 准备插入语句 stmt, err := tx.Prepare(insertSQL) if err != nil { log.Fatal(err) } // 遍历数据 values := make([]interface{}, len(columns)) valuePtrs := make([]interface{}, len(columns)) for rows.Next() { for i := range columns { valuePtrs[i] = &values[i] } rows.Scan(valuePtrs...) // 处理特殊类型转换 for i, v := range values { if b, ok := v.([]byte); ok { values[i] = string(b) } } _, err = stmt.Exec(values...) if err != nil { log.Fatal(err) } } rows.Close() } tx.Commit() ``` ### 七、注意事项 1. **类型转换**:需要特别注意BOOLEAN类型和日期格式的转换 2. **索引和外键**:需要单独处理约束关系 3. **字符编码**:建议统一使用UTF-8编码 4. **事务处理**:大数据量时需分批提交事务 5. **性能优化**:可考虑使用`PRAGMA synchronous = OFF`提高导入速度 ### 八、完整示例代码结构 ``` main.go ├── connectDatabases() // 数据库连接 ├── getTableStructure() // 获取表结构 ├── convertDataTypes() // 数据类型转换 ├── createSQLiteTables() // 创建新表 └── migrateData() // 数据迁移 ``` 实际使用时需要根据具体表结构进行调整,建议先在小数据量表上进行测试,验证数据完整性后再进行完整迁移。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值