欢迎来到我的博客,愿共同成长!!!

信徒

路虽远,行则将至

基于mysql的数据库基本操作及底层原理的简单解析

本文通过介绍面向mysql数据库的基本操作,包括;建库建表以及最基本的增删查改,来简单探究database/sql以及github.com/go-sql-driver/mysql的实现原理。

1. 创建数据库

使用Open()初始化相关的资源和配置,在使用Open()时不需要指定数据库的名字(因为此时还没有数据库),代码如下:

 代码块一
1
import ( 2 "database/sql" 3 "fmt" 4 5 _ "github.com/go-sql-driver/mysql" 6 ) 7 ... 8 var err error 9 sqlStat := fmt.Sprintf("%s:%s@(%s)/", "root", "123", "127.0.0.1:3306") 10 dbtmp, err := sql.Open("mysql", sqlStat) 11 if err != nil { 12 panic(err) 13 }

那Open()在底层做了什么,让我们来一探究竟

 代码块二
1
func Open(driverName, dataSourceName string) (*DB, error) { 2 driversMu.RLock() 3 driveri, ok := drivers[driverName] 4 driversMu.RUnlock() 5 if !ok { 6 return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName) 7 } 8 9 if driverCtx, ok := driveri.(driver.DriverContext); ok { 10 connector, err := driverCtx.OpenConnector(dataSourceName) 11 if err != nil { 12 return nil, err 13 } 14 return OpenDB(connector), nil 15 } 16 17 return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil 18 }

如上,首先从数据库驱动缓存里面取出mysql对应的驱动器,读者可能会问到:此驱动缓存是何时将各种数据库的驱动添加进去的呢?

其实它不是由于sql包主动添加的,而是sql包对外提供了一个Register()接口,用于注册数据库驱动,代码如下:

 代码块三
1
func Register(name string, driver driver.Driver) { 2 driversMu.Lock() 3 defer driversMu.Unlock() 4 if driver == nil { 5 panic("sql: Register driver is nil") 6 } 7 if _, dup := drivers[name]; dup { 8 panic("sql: Register called twice for driver " + name) 9 } 10 drivers[name] = driver 11 }

那mysql是何时注册自己的数据库驱动的呢?我们看到代码块一的第5行,在包名之前添加下划线的作用是只执行该包的init方法,而不需要导入整个包。关于Go语言程序模块的初始化顺序,参见https://studygolang.com/articles/6464

那我们再来看看mysql数据库驱动包中的init方法做了什么:

1 func init() {
2     sql.Register("mysql", &MySQLDriver{})
3 }

果然如此,mysql数据库驱动包在初始化的时候向sql包注册了自己的驱动。

我们回到代码块二,在第9行,直接将driveri断言成了DriverContext接口类型,这里driveri虽然是Driver类型,但是由上述分析可知其动态值是MySQLDriver类型,根据Go语言特性,一个接口类型可直接断言成另外一个接口类型,那么其动态类型必须实现后者,所以我们看看MySQLDriver是否实现了DriverContext接口:

 代码块四
1
// If a Driver implements DriverContext, then sql.DB will call 2 // OpenConnector to obtain a Connector and then invoke 3 // that Connector's Conn method to obtain each needed connection, 4 // instead of invoking the Driver's Open method for each connection. 5 // The two-step sequence allows drivers to parse the name just once 6 // and also provides access to per-Conn contexts. 7 type DriverContext interface { 8 // OpenConnector must parse the name in the same format that Driver.Open 9 // parses the name parameter. 10 OpenConnector(name string) (Connector, error) 11 }
 代码块五
1
// OpenConnector implements driver.DriverContext. 2 func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error) { 3 cfg, err := ParseDSN(dsn) 4 if err != nil { 5 return nil, err 6 } 7 return &connector{ 8 cfg: cfg, 9 }, nil 10 }

答案YES。我们从代码块四的解释中可以大致知道DriverContext接口的作用是让不同数据库驱动包提供工厂方法(OpenConnector)来获取其实现的Connector接口实例。而Connector接口定义如下:

 代码块六
1
// A Connector represents a driver in a fixed configuration 2 // and can create any number of equivalent Conns for use 3 // by multiple goroutines. 4 // 5 // A Connector can be passed to sql.OpenDB, to allow drivers 6 // to implement their own sql.DB constructors, or returned by 7 // DriverContext's OpenConnector method, to allow drivers 8 // access to context and to avoid repeated parsing of driver 9 // configuration. 10 type Connector interface { 11 // Connect returns a connection to the database. 12 // Connect may return a cached connection (one previously 13 // closed), but doing so is unnecessary; the sql package 14 // maintains a pool of idle connections for efficient re-use. 15 // 16 // The provided context.Context is for dialing purposes only 17 // (see net.DialContext) and should not be stored or used for 18 // other purposes. 19 // 20 // The returned connection is only used by one goroutine at a 21 // time. 22 Connect(context.Context) (Conn, error) 23 24 // Driver returns the underlying Driver of the Connector, 25 // mainly to maintain compatibility with the Driver method 26 // on sql.DB. 27 Driver() Driver 28 }

从解释中可以看出Connector接口主要用于与mysql server创建连接。而在代码块五中工厂方法OpenConnector确实返回了一个实现了Connector接口的结构体connector实例。

再次回到代码块二,接下来调用OpenDB(),如下:

 1 func OpenDB(c driver.Connector) *DB {
 2     ctx, cancel := context.WithCancel(context.Background())
 3     db := &DB{
 4         connector:    c,
 5         openerCh:     make(chan struct{}, connectionRequestQueueSize),
 6         resetterCh:   make(chan *driverConn, 50),
 7         lastPut:      make(map[*driverConn]string),
 8         connRequests: make(map[uint64]chan connRequest),
 9         stop:         cancel,
10     }
11 
12     go db.connectionOpener(ctx)
13     go db.connectionResetter(ctx)
14 
15     return db
16 }

上述代码先是初始化配置和资源,然后启动一个goroutine调用connectionOpener()来监听新建连接的请求。

总结:所以Open()的作用主要是初始化配置和资源,并监听新建连接的请求。

调用了Open()之后,接着调用Exec()执行sql语句创建数据库,代码如下:

1     sqlStat = fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", "mydb")
2     _, err = dbtmp.Exec(sqlStat)
3     if err != nil {
4         panic(err)
5     } 

如果追踪下去,会发现实际上是从连接池选取一条连接或者新建一条的连接,将sql语句发送过去执行。

由于前面调用Open()时没有指定具体的数据库,因此底层与mysql server建立连接时并不会指定使用具体哪个数据库,所以应用程序后续进行数据库操作时(如CRUD),必须首先执行如下语句,用于与mysql server建立指定具体数据库的连接:

1     sql = fmt.Sprintf("USE %s", DBName)
2     _, err = db.Exec(sql)

这样比较麻烦,所以我们在创建数据库之后,可以重新创建一个DB实例,同时指定刚刚创建的数据库的名字。代码如下:

1     sqlStat = fmt.Sprintf("%s:%s@(%s)/%s", "root", "123", "127.0.0.1:3306", "mydb")
2     db, err = sql.Open("mysql", sqlStat)
3     if err != nil {
4         panic(err)
5     }

而返回的DB实例通常作为全局变量在程序的整个生命周期用于对数据库的操作。

2. CRUD

这一部分内容为对数据库表记录最基本的增删查改操作。

1)添加记录

 1 /**
 2  * Insert single record of 'tableName'
 3  * @params: value is the record to Insert(field name and value pairs)
 4  * @return: error info
 5  */
 6 
 7 func Insert(tableName string, value map[string]interface{}) error {
 8     sqlStr := "INSERT " + tableName + " SET"
 9     var args []interface{}
10     for k, v := range value {
11         sqlStr = sqlStr + " " + k + "=?,"
12         args = append(args, v)
13     }
14     //remove the last ','
15     sqlArr := []byte(sqlStr)
16     sqlStr = string(sqlArr[:len(sqlArr)-1])
17 
18     // prepare
19     stmt, err := db.Prepare(sqlStr)
20     if err != nil {
21         logger.Error("Prepare sql error: ", err.Error())
22         return fmt.Errorf("Insert data failed.")
23     }
24     // exec
25     _, err = stmt.Exec(args...)
26     if err != nil {
27         logger.Error("Exec sql error: ", err.Error())
28         return fmt.Errorf("Insert data failed.")
29     }
30 
31     return nil
32 }

2)删除记录

 1 /**
 2  * Delete record of 'tableName'
 3  * @params: condition is used to find the records to be deleted, and is table field name-value pairs
 4  * @return: error info
 5  */
 6 
 7 func Delete(tableName string, condition map[string]interface{}) error {
 8     // join sql statement
 9     sqlStr := "DELETE FROM " + tableName + " WHERE"
10     var args []interface{}
11     for k, v := range condition {
12         sqlStr = sqlStr + " " + k + "=?,"
13         args = append(args, v)
14     }
15     //remove the last ','
16     sqlArr := []byte(sqlStr)
17     sqlStr = string(sqlArr[:len(sqlArr)-1])
18 
19     // prepare
20     stmt, err := db.Prepare(sqlStr)
21     defer stmt.Close()
22     if err != nil {
23         logger.Error("Prepare sql error: ", err.Error())
24         return fmt.Errorf("Delete data failed")
25     }
26     // exec
27     _, err = stmt.Exec(args...)
28     if err != nil {
29         logger.Error("Exec sql error: ", err.Error())
30         return fmt.Errorf("Delete data failed")
31     }
32 
33     return nil
34 }

3)查询记录(可查多条记录)

 1 /**
 2  * Query records of 'tableName'
 3  * @params: condition is used to find the records, and is table field name-value pairs
 4  * @return: query result records encapsulated into a map(for now, all values' type are string)
 5  *          error info
 6  */
 7 
 8 func Query(tableName string, condition map[string]interface{}) ([]map[string]interface{}, error) {
 9     // join sql statement
10     sqlStr := "SELECT * FROM " + tableName
11     var args []interface{}
12     if len(condition) != 0 {
13         sqlStr += " WHERE"
14         for k, v := range condition {
15             sqlStr = sqlStr + " " + k + "=? AND"
16             args = append(args, v)
17         }
18         //remove the last 'and'
19         sqlArr := []byte(sqlStr)
20         sqlStr = string(sqlArr[:len(sqlArr)-3])
21     }
22     // query
23     rows, err := db.Query(sqlStr, args...)
24     if err != nil {
25         logger.Error("Query error: ", err.Error())
26         return nil, fmt.Errorf("Query data failed")
27     }
28     defer rows.Close()
29     cols, err := rows.Columns() //get table fields name
30     if err != nil {
31         logger.Error("Get columns error: ", err.Error())
32         return nil, fmt.Errorf("Query data failed")
33     }
34     colsLen := len(cols)
35     var queryResult []map[string]interface{} //query result that may include multiple records.
36     for rows.Next() {
37         beans := make([]interface{}, colsLen)
38         for i := 0; i < colsLen; i++ {
39             beans[i] = &sql.NullString{}
40         }
41         err := rows.Scan(beans...)
42         if err != nil {
43             logger.Error("Scan record  from table error: ", err.Error())
44         }
45         queryResult = append(queryResult, toMap(beans, cols))
46     }
47 
48     return queryResult, nil
49 }

需要解释的是,我把查询出来的记录转化成了map切片,切片的每个元素为一条记录,map的key为数据库表中的字段名,value为值,这里有个缺陷就是value都转换成了string类型。

4)更改记录(只支持最简单的UPDATE语句,即为:UPDATE [table name] SET [fields name and value to be changed] WHERE [condition fileds name and value])

 1 /**
 2  * update record of 'tableName'
 3  * @params: condition is used to find the records
 4  *          value includes names and values of the fields to be updated
 5  * @return: error info
 6  */
 7 
 8 func Update(tableName string, condition, value map[string]interface{}) error {
 9     // join sql statement
10     sqlStr := "UPDATE " + tableName + " SET"
11     var args []interface{}
12     for k, v := range value {
13         sqlStr = sqlStr + " " + k + "=?,"
14         args = append(args, v)
15     }
16     //remove the last ','
17     sqlArr := []byte(sqlStr)
18     sqlStr = string(sqlArr[:len(sqlArr)-1])
19     sqlStr += " WHERE"
20     for k, v := range condition {
21         sqlStr = sqlStr + " " + k + "=? AND"
22         args = append(args, v)
23     }
24     //remove the last 'and'
25     sqlArr = []byte(sqlStr)
26     sqlStr = string(sqlArr[:len(sqlArr)-3])
27     // prepare
28     stmt, err := db.Prepare(sqlStr)
29     if err != nil {
30         logger.Error("Prepare sql error: ", err.Error())
31         return fmt.Errorf("Update data failed")
32     }
33     // exec
34     _, err = stmt.Exec(args...)
35     if err != nil {
36         logger.Error("Exec sql error: ", err.Error())
37         return fmt.Errorf("Update data failed")
38     }
39 
40     return nil
41 }

 

posted @ 2019-09-26 17:17  LeslieRan  阅读(446)  评论(0)    收藏  举报