golang operation mysql use summary

 

sql.DB provides us with open and close operations to manage the underlying database connection through the database driver.
sql.DB manages the database connection pool for us . It
should be noted that sql.DB represents an abstract access interface for operating the database, not a database connection object ; It can open and close database connections according to the driver, and manage the connection pool. The connection in use is marked as busy, and after it is used up, it returns to the connection pool to wait for the next use. So, if you don't release the connection back to the connection pool, it will cause too many connections to exhaust system resources.

operate mysql

1. Import the mysql database driver

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

 

Generally speaking, you should not use the method provided by the driver directly, but should use sql.DB, so when importing the mysql driver, the anonymous import method (add _ before the package path) is used here. When a database is imported After the driver, the driver will initialize and register itself in the Golang database/sql context, so we can access the database through the methods provided by the database/sql package.

2. Connect to the database

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()
}

 

Return one by calling the sql.Open function sql.DB指针; the sql.Open function prototype is as follows:

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

 

  • driverName: The name of the driver used. This name is actually the name used by the database driver when it is registered to database/sql.
  • dataSourceName: Database connection information, this connection includes the database user name, password, database host and the database name that needs to be connected.
  1. sql.Open does not immediately establish a network connection to the database, nor does it verify the validity of the database connection parameters, it just initializes a sql.DB object. When the first database query operation is actually performed, at this time Only then will the actual network connection be established;
  2. sql.DB represents the object of the abstract interface for operating the database, but it is not the so-called database connection object. The sql.DB object will create a connection only when it needs to be used. If you want to verify the connection immediately, you need to use the Ping() method;
  3. The sql.DB object returned by sql.Open is coroutine concurrency safe.
  4. sql.DB is designed to be used as a persistent connection. Don't open and close frequently. A better practice is to create a DB object for each different datastore and keep these objects Open. If a short connection is required, pass DB as a parameter to the function instead of Open and Close in the function.

3. Basic database operations

The general steps for a database query are as follows:

  1. Call db.Query to execute the SQL statement, this method will return a Rows as the result of the query
  2. Iteratively query data through rows.Next().
  3. Read the value of each row through rows.Scan()
  4. Call db.Close() to close the query

The existing userdatabase tables are as follows:

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

 

Before MySQL 5.5, UTF8 encoding only supports 1-3 bytes. Starting from MYSQL5.5, it can support 4-byte UTF encoding utf8mb4, and a character can have up to 4 bytes. utf8mb4 is compatible with utf8, so it can support more Character set; for emoji expressions, mysql's utf8 is not supported, and it needs to be modified and set to utf8mb4 to support it.

Query data

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. The order of the rows.Scan parameter is very important, it needs to correspond to the column of the query result. For example "SELECT * From user where age >=20 AND age < 30" the column order of the query row is "id, name, age" and insert The order of operations is the same, so rows.Scan also needs to be in this order rows.Scan(&id, &name, &age), otherwise it will cause misalignment of data reading.
  2. Because golang is a strongly typed language, the data type is first defined when querying data, but there are three possibilities for querying data in the database: there are three states: value exists, zero value exists, and NULL is not assigned, because the data type to be queried can be defined For the sql.Nullxxx type, you can judge whether the queried value is in the assigned state or the unassigned NULL state by judging the Valid value.
  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()

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325470488&siteId=291194637