一 . Go 数据库简介
在Go中访问数据库需要用到sql.DB接口:它可以创建语句(statement)和事务(transaction),执行查询,获取结果。
sql.DB并不是数据库连接,也并未在概念上映射到特定的数据库(Database)或模式(schema)。它只是一个抽象的接口,不同的具体驱动有着不同的实现方式。通常而言,sql.DB会处理一些重要而麻烦的事情,例如操作具体的驱动打开/关闭实际底层数据库的连接,按需管理连接池。
sql.DB这一抽象让用户不必考虑如何管理并发访问底层数据库的问题。当一个连接在执行任务时会被标记为正在使用。用完之后会放回连接池中。不过用户如果用完连接后忘记释放,就会产生大量的连接,极可能导致资源耗尽(建立太多连接,打开太多文件,缺少可用网络端口)
二 .常用的API及使用
1.导入驱动
2.建立连接
3. 单行查询
4.多行查询
5. 添加记录
6. 删除操作
7. 更新操作
8.事务
特别提醒:
》资源耗尽
不谨慎地使用database/sql会给自己挖许多坑,最常见的问题就是资源枯竭(resource exhaustion):
打开和关闭数据库(sql.DB)可能会导致资源枯竭;
结果集没有读取完毕,或者调用rows.Close()失败,结果集会一直占用池里的连接;
使用Query()执行一些不返回结果集的语句,返回的未读取结果集会一直占用池里的连接;
不了解准备语句(Prepared Statement)的工作原理会产生许多额外的数据库访问。
》Uint64
Go底层使用int64来表示整型,使用uint64时应当极其小心。使用超出int64表示范围的整数作为参数,会产生一个溢出错误:
// Error: constant 18446744073709551615 overflows int_, err := db.Exec("INSERTINTOusers(id)VALUES", math.MaxUint64)
这种类型的错误非常不容易发现,它可能一开始表现的很正常,但是溢出之后问题就来了
》不合预期的连接状态
连接的状态,例如是否处于事务中,所连接的数据库,设置的变量等,应该通过Go的相关类型来处理,而不是通过SQL语句。用户不应当对自己的查询在哪条连接上执行作任何假设,如果需要在同一条连接上执行,需要使用Tx。
举个例子,通过USE DATABASE改变连接的数据库对于不少人是习以为常的操作,执行这条语句,只影响当前连接的状态,其他连接仍然访问的是原来的数据库。如果没有使用事务Tx,后续的查询并不能保证仍然由当前的连接执行,所以这些查询很可能并不像用户预期的那样工作。
更糟糕的是,如果用户改变了连接的状态,用完之后它成为空连接又回到了连接池,这会污染其他代码的状态。尤其是直接在SQL中执行诸如BEGIN或COMMIT这样的语句
》驱动的特殊语法
尽管database/sql是一个通用的抽象,但不同的数据库,不同的驱动仍然会有不同的语法和行为。参数占位符就是一个例子。
》批量操作
出乎意料的是,标准库没有提供对批量操作的支持。即INSERT INTO xxx VALUES (1),(2),...;这种一条语句插入多条数据的形式。目前实现这个功能还需要自己手动拼SQL。
》执行多条语句
database/sql并没有对在一次查询中执行多条SQL语句的显式支持,具体的行为以驱动的实现为准。所以对于
_, err := db.Exec("DELETEFROM tbl1;DELETEFROM tbl2") // Error/unpredictable result
这样的查询,怎样执行完全由驱动说了算,用户并无法确定驱动到底执行了什么,又返回了什么。
》事务中的多条语句
因为事务保证在它上面执行的查询都由同一个连接来执行,因此事务中的语句必需按顺序一条一条执行。对于返回结果集的查询,结果集必须Close()之后才能进行下一次查询。用户如果尝试在前一条语句的结果还没读完前就执行新的查询,连接就会失去同步。这意味着事务中返回结果集的语句都会占用一次单独的网络往返。
网友评论