TSV文件和CSV文件都是文本文件。
TSV,Tab-separated values,TSV文件的每一行用制表符(按Tab键输入’\t’)作为字段值的分隔符。
CSV,Comma-separated values,CSV文件的每一行用半角逗号(’,’)作为字段值的分隔符。
TSV文件和CSV文件都是文本文件。
TSV,Tab-separated values,TSV文件的每一行用制表符(按Tab键输入’\t’)作为字段值的分隔符。
CSV,Comma-separated values,CSV文件的每一行用半角逗号(’,’)作为字段值的分隔符。
本文翻译自《Querying for data》。
目录
执行返回行数据的SQL语句时,请使用database/sql包中提供的Query方法之一。其中每一个都返回一行或多行,你可以使用Scan方法将其数据复制到变量中。例如,你可以使用这些方法来执行SELECT语句。
执行不返回行数据的语句,可以改用Exec或ExecContext方法。有关详细信息,请参阅执行不返回数据的SQL语句。
database/sql包提供了两种执行查询并返回结果的方法:
QueryRow方法从数据库中最多返回一行作为一个Row结构体。有关详细信息,请参阅查询单行。Query方法返回所有匹配的行作为代码可以循环的Rows结构体。有关详细信息,请参阅查询多行。如果你的代码将重复执行相同的SQL语句,请考虑使用准备好的语句(译者注:预处理语句)。有关详细信息,请参阅使用准备好的语句。
注意:不要使用fmt.Sprintf等字符串拼接函数来组装SQL语句!否则你可能会引入SQL注入风险。有关更多信息,请参阅避免SQL注入风险。
查询单行
QueryRow函数最多检索单个数据库行,例如通过唯一ID查找数据时。如果查询返回多行,则Scan方法会丢弃除第一行以外的所有行。
QueryRowContext函数的工作方式类似于QueryRow,但带有context.Context参数。有关更多信息,请参阅取消正在进行的数据库操作。
以下示例使用查询来确定是否有足够的库存来支持购买。如果足够,则SQL语句返回true,否则返回false。Row.Scan方法通过指针将布尔返回值复制到enough变量中。
func canPurchase(id int, quantity int) (bool, error) {
var enough bool
// 查询基于单行的值。
if err := db.QueryRow("SELECT (quantity >= ?) from album where id = ?",
quantity, id).Scan(&enough); err != nil {
if err == sql.ErrNoRows {
return false, fmt.Errorf("canPurchase %d: unknown album", id)
}
return false, fmt.Errorf("canPurchase %d: %v", id)
}
return enough, nil
}
注意:预处理语句中的参数占位符因所使用的DBMS和驱动程序而异。例如,Postgres的pq驱动程序需要像$1这样的占位符,而MySQL驱动程序使用?号。
处理错误
QueryRow函数本身不返回错误。相反,Scan方法会报告查找和扫描中的任何错误。当查询未找到任何行时,它返回sql.ErrNoRows错误。
以下列出返回单行的函数
单独运行单行查询。
在较大的事务中运行单行查询。有关更多信息,请参阅执行事务。
使用一个准备好的语句运行单行查询。有关更多信息,请参阅使用准备好的语句。
与专用的数据库连接一起使用。有关更多信息,请参阅管理数据库连接。
查询多行
你可以使用Query或QueryContext函数查询多行,它们返回表示查询结果的Rows结构体。你的代码使用rows.Next方法对返回的行进行迭代。每次迭代都调用Scan方法将列值复制到变量中。
QueryContext函数的工作方式与Query类似,但带有context.Context参数。有关更多信息,请参阅取消正在进行中的数据库操作。
以下示例执行查询以返回指定艺术家(artist)的专辑(album)信息。album在sql.Rows中返回。以下代码使用Rows.Scan函数将列值复制到由指针指出的变量中。
func albumsByArtist(artist string) ([]Album, error) {
rows, err := db.Query("SELECT * FROM album WHERE artist = ?", artist)
if err != nil {
return nil, err
}
defer rows.Close()
// 一个切片用来存储返回的行数据。
var albums []Album
// 循环遍历行数据,使用Scan方法把每一行的各列的值,赋值给一个结构体变量的对应字段。Loop through rows, using Scan to assign column data to struct fields.
for rows.Next() {
var alb Album
if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist,
&alb.Price, &alb.Quantity); err != nil {
return albums, err
}
albums = append(albums, album)
}
if err = rows.Err(); err != nil {
return albums, err
}
return albums, nil
}
请注意对rows.Close函数的延迟调用。无论函数如何返回,这都会释放sql.Rows占有的所有资源。虽然循环遍历完毕所有的行也会隐式关闭它,但最好使用defer来确保无论如何都能关闭。
注意:预处理语句中的参数占位符因所使用的DBMS和驱动程序而异。例如,Postgres的pq驱动程序需要像$1这样的占位符,而MySQL驱动程序使用?号。
处理错误
请务必在遍历查询结果后检查sql.Rows中是否有错误发生。如果查询失败,这就是你的代码发现错误的方式。
以下列出返回多行数据的函数
单独运行查询。
在较大的事务中执行查询。有关更多信息,请参阅执行事务。
使用一个准备好的语句运行查询。有关更多信息,请参阅使用准备好的语句。
与专用的数据库连接一起使用。有关更多信息,请参阅管理数据库连接。
处理可null列的值
当列的值可能为null时,database/sql包提供了几种特殊类型,可以用作Scan函数的参数。每个特殊类型都包括一个Valid字段,该字段报告列值是否为非null,如果为非null则该类型有另一个字段包含该列值。
以下示例中的代码用于查询客户名称。如果名称值为null,则代码将用另一个值替换它,以在应用程序中使用。
var s sql.NullString
err := db.QueryRow("SELECT name FROM customer WHERE id = ?", id).Scan(&s)
if err != nil {
log.Fatal(err)
}
name := "Valued Customer"
// 如果客户名称不为null(合法,s.Valid的值为true),那么赋值给name变量
if s.Valid {
name = s.String
}
请参阅sql包关于每个特殊类型的更多信息:
从列中获取数据
在查询返回的行上循环遍历时,可以使用Scan函数将行的列值复制到Go值中,例如rows.Scan的参考文档中所述。
所有驱动程序都支持一组基本的数据类型转换,例如将SQL的INT转换为Go的INT。一些驱动程序扩展了这组转换;有关详细信息,请参阅每个驱动程序的文档。
正如你所料,Scan函数将把列类型转换为类似的Go类型。例如,Scan将把SQL的CHAR、VARCHAR和TEXT转换为Go字符串。但是,Scan还可以把列值转换为另一个非常适合的Go类型。例如,如果列是始终包含数字的VARCHAR,则可以指定数字Go类型(例如int)来接收值,Scan将使用strconv.Atoi函数进行转换。
有关Scan函数进行类型转换的更多详细信息,请参阅Rows.Scan参考文档。
处理多个结果集
当数据库操作可能返回多个结果集时,你可以使用Rows.NextResultSet函数来检索这些结果集。例如,当你发送分别查询多个表的一个SQL语句,并为其中每个表都返回一个结果集时,这可能很有用。
Rows.NextResultSet函数会准备下一个结果集,以便调用Rows.Next函数从下一个集合中检索第一行。它返回一个布尔值,指示是否存在下一个结果集。
以下示例中的代码使用DB.Query函数来执行两条Select语句。第一个结果集来自第一个Select查询,检索相簿album表中的所有行。下一个结果集来自第二个Select查询,从歌曲表song中检索行。
rows, err := db.Query("SELECT * from album; SELECT * from song;")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// 循环遍历第一个结果集。
for rows.Next() {
// 处理结果集…
}
// 前进到下一个结果集。
rows.NextResultSet()
// 循环遍历第二个结果集。
for rows.Next() {
// 处理结果集…
}
// 检查在处理所有结果集的过程中,是否有错误发生。
if err := rows.Err(); err != nil {
log.Fatal(err)
}
本文翻译自《Avoiding SQL injection risk》。
你可以通过提供SQL参数值作为sql包里函数的参数来避免SQL注入风险。sql包中的许多函数为SQL语句(包括准备语句)的参数提供参数值。
以下示例中的代码使用?作为SQL语句的参数的占位符,由Query函数的id参数提供值:
// 使用参数执行一个SQL语句的正确格式
rows, err := db.Query("SELECT * FROM user WHERE id = ?", id)
执行数据库操作的sql包的函数根据你提供的参数创建SQL准备语句。在运行时,sql包将SQL语句转换为准备语句并将其与参数分开发送。
注意:参数占位符因你使用的DBMS和驱动程序而异。例如,Postgres的pq驱动程序使用$1形式的占位符形式,而不是?号。
你可能想使用fmt包中的函数将SQL语句组装为包含参数值的字符串——如下所示:
// 有安全风险!
rows, err := db.Query(fmt.Sprintf("SELECT * FROM user WHERE id = %s", id))
这不安全!执行此操作时,Go会组装整个SQL语句,在将完整语句发送到DBMS之前用参数值替换%s格式动词。这会带来SQL注入风险,因为代码的调用者可能会发送意外的SQL片段作为id参数。该片段可能会以不可预测的方式执行SQL语句,这对你的应用程序是危险的。
例如,通过传递一个特定的%s值,你可能会得到类似于以下内容的SQL语句,它可能会返回数据库中的所有用户记录:
SELECT * FROM user WHERE id = 1 OR 1=1;
本文翻译自《Using prepared statements》。
目录
你可以定义准备好的语句以供重复使用。这可以避免每次代码执行数据库操作时重新创建语句的额外开销,从而帮助你的代码运行得更快一些。
注意:准备好的语句中的参数占位符因你使用的DBMS和驱动程序而异。例如Postgres的pq驱动程序需要$1形式的占位符,而不是?号占位符。
什么是准备好的语句?
准备好的语句是由DBMS解析和保存的SQL,通常包含占位符但没有实际参数值。稍后,可以使用一组参数值来执行该语句。(译者注:准备好的语句,准备语句,也叫预处理语句。)
如何使用准备好的语句
当你希望重复执行同一条SQL时,可以使用一个sql.Stmt结构体预先准备好SQL语句,然后按需执行。
以下示例创建一个准备好的语句,从数据库中选择一个特定的专辑(album)的信息。DB.Prepare函数返回一个sql.Stmt结构体,代表给定SQL文本的准备语句。你可以将SQL语句的参数值传递给Stmt.Exec、Stmt.QueryRow或Stmt.Query方法以运行该语句。
// AlbumByID函数返回一个特定的专辑(album)的信息。
func AlbumByID(id int) (Album, error) {
// 定义准备好的语句。你通常会在别处定义该语句并将其保存以供在诸如本函数之类的函数中使用。
stmt, err := db.Prepare("SELECT * FROM album WHERE id = ?")
if err != nil {
log.Fatal(err)
}
var album Album
// 执行准备好的语句,为占位符为?的参数传入一个id值。
err := stmt.QueryRow(id).Scan(&album.ID, &album.Title, &album.Artist, &album.Price, &album.Quantity)
if err != nil {
if err == sql.ErrNoRows {
// 处理没有一行数据返回的情况。
}
return album, err
}
return album, nil
}
准备好的语句的行为
准备好的sql.Stmt提供常用的Exec、QueryRow和Query方法来调用语句。有关使用这些方法的更多信息,请参阅查询数据和执行不返回数据的SQL语句。
但是,由于sql.Stmt已经代表了一条预设的SQL语句,所以它的Exec、QueryRow、Query方法只需占位符对应的参数值,省略了SQL文本。
你可以用不同的方式定义一个新的sql.Stmt,这取决于你将如何使用它。
DB.Prepare和DB.PrepareContext方法创建一个准备好的语句,它可以在事务之外单独执行,就像DB.Exec和DB.Query方法一样。Tx.Prepare、Tx.PrepareContext、Tx.Stmt和Tx.StmtContext方法创建用于特定事务的准备好的语句。Prepare和PrepareContext方法使用SQL文本来定义语句。Stmt和StmtContext方法使用DB.Prepare或DB.PrepareContext方法的结果。也就是说,它们将一个非事务的sql.Stmt转换为一个事务的sql.Stmt。Conn.PrepareContext方法从sql.Conn方法创建准备好的语句,表示专用的连接。当你的代码结束时,请确保调用stmt.Close方法。这将释放可能与其关联的任何数据库资源(例如底层连接)。对于函数中仅作为局部变量的语句,defer stmt.Close()就足够了。
用于创建准备好的语句的函数
DB.Prepare
DB.PrepareContext
准备一个单独执行的语句,或者使用Tx.Stmt方法把它转换为事务中的准备语句。
Tx.Prepare
Tx.PrepareContext
Tx.Stmt
Tx.StmtContext
准备用于特定事务的语句。有关更多信息,请参阅执行事务。
Conn.PrepareContext
与专用的连接一起使用。有关更多信息,请参阅管理数据库连接。
本文翻译自《Managing connections》。
目录
对于绝大多数程序,你都无需调整sql.DB连接池的默认值。但对于某些高级程序,你可能需要调整连接池参数或显式地处理连接。本文教你如何操作。
sql.DB数据库句柄对于多个goroutine并发使用是安全的(这意味着该句柄是其他编程语言可能称之为“线程安全(thread-safe)”的)。其他一些数据库访问库基于每次只能进行一个操作的数据库连接方式。为了弥补这一差距,每个sql.DB都管理一个连接到底层数据库的活动连接池,根据需要可以在Go程序中创建新的并行的连接。
连接池适用于大多数的数据库访问需求。当你调用sql.DB的Query或Exec方法时,sql.DB会从池中检索可用的连接,或者在需要时创建一个连接。当不再需要连接时,它会将连接返回到池。这种方式能支持大规模并行访问数据库。
设置连接池属性
你可以设置属性来指导sql包如何管理连接池。要获取有关这些属性效果的统计信息,请使用DB.Stats方法。
设置能打开的连接的最大数量
DB.SetMaxOpenConns方法对打开的连接数量进行了限制。超过此限制,新的数据库操作将等待现有操作完成,然后sql.DB将创建另一个连接。默认情况下,当需要连接时,sql.DB会在所有现有连接都在使用时创建一个新连接。
请记住,设置连接数限制会让Go程序连接数据库时使用类似于获取锁或信号量的机制,从而导致程序在等待新的数据库连接时可能会被阻塞等待。
设置空闲连接的最大数量
DB.SetMaxIdleConns方法更改sql.DB维护的最大空闲连接数。
当SQL操作在给定的数据库连接上完成时,通常不会立即关闭这个连接:应用程序可能很快再次需要使用该连接,保持打开的连接可以避免在下一次操作时重新连接到数据库。默认情况下,sql.DB在任何给定时刻保持两个空闲连接。提高最大空闲连接数可以避免在大规模并行程序中频繁重新建立连接。
设置连接可以空闲的最大时间
DB.SetConnMaxIdleTime方法设置连接在关闭之前可以处于空闲状态的最大时间长度。这会导致sql.DB关闭空闲时间超过给定持续时间的连接。
默认情况下,当一个空闲连接被添加到连接池时,它会一直保持在那里,直到再次需要它为止。使用DB.SetMaxIdleConns函数来增加并行活动突发期间允许的空闲连接数,也可以使用DB.SetConnMaxIdleTime函数来安排稍后在系统安静时释放这些连接。
设置连接的最大生存时间
使用DB.SetConnMaxLifetime函数设置连接在关闭之前可以保持打开的最大时间长度。
默认情况下,连接可以在任意长的时间内使用和重用,但要遵守上述限制。在某些系统中,例如使用负载均衡数据库服务器的系统,确保应用程序在不重新连接的情况下永远不会使用特定连接太长时间,这可能会很有用。
使用专用连接
当某个数据库可能为在一个特殊连接上执行的一系列操作指定隐式含义时,database/sql包提供可以使用的函数。
最常见的例子是事务,通常以BEGIN命令开始,以COMMIT或ROLLBACK命令结束,并在这个事务的连接上发出所有命令(包括BEGIN、COMMIT或ROLLBACK这几个命令)。对于这个例子,请使用sql包的事务支持。请参阅执行事务。
对于其他必须在同一个连接上执行一系列单独操作的例子,sql包提供了专用连接。DB.Conn获得一个专用连接sql.Conn。sql.Conn有BeginTx、ExecContext、PingContext、PrepareContext、QueryContext和QueryRowContext方法,它们的行为与DB上的等效方法类似,但只使用专用的连接。专用连接使用完毕后,你的代码必须使用Conn.Close将其释放。
本文翻译自《Executing SQL statements that don’t return data》。
执行不返回数据的数据库操作时,请使用database/sql包中的Exec或ExecContext方法。以这种方式执行的SQL语句包括INSERT、DELETE和UPDATE。
当查询可能返回行数据时,请改Query或QueryContext方法。有关详细信息,请参阅查询一个数据库。
ExecContext方法的工作原理与Exec方法相同,但有一个额外的context.Context参数,具体作用参见“取消正在进行中的数据库操作”。
以下示例中的代码使用DB.Exec执行一条语句,将一条唱片的信息添加到album表中。
func AddAlbum(alb Album) (int64, error) {
result, err := db.Exec("INSERT INTO album (title, artist) VALUES (?, ?)", alb.Title, alb.Artist)
if err != nil {
return 0, fmt.Errorf("AddAlbum: %v", err)
}
// 获取新插入的这条记录的ID
id, err := result.LastInsertId()
if err != nil {
return 0, fmt.Errorf("AddAlbum: %v", err)
}
// 返回这条新记录的ID
return id, nil
}
DB.Exec返回值:一个sql.Result和一个指示是否有错误发生的变量。当错误为nil时,你可以使用Result来获取最后插入项的ID(如示例中所示)或获取数据表中受操作影响的行数。
注意:预处理语句中的参数占位符因所使用的DBMS和驱动程序而异。例如,Postgres的pq驱动程序需要像$1这样的占位符,而不是?。
如果你的代码将重复执行相同的SQL语句,请考虑使用SQL.Stmt从SQL语句中创建一个可重复使用的准备语句。有关详细信息,请参阅使用准备好的语句。
注意:不要使用fmt.Sprintf等字符串格式函数来组装SQL语句!因为可能会有SQL注入风险。有关更多信息,请参阅避免SQL注入风险。
以下列出用于执行不返回行数据的SQL语句的函数
孤立地执行单个SQL语句。
在较大的事务中执行SQL语句。有关详细信息,请参阅执行事务。
执行已准备好的SQL语句。有关详细信息,请参阅使用准备好的语句。
用于专用的连接。有关详细信息,请参阅管理连接。
1 下载 header-editor 插件并安装。
2 添加一个要修改的请求头x-forwarded-for,值为1.1.1.1。
3 VPN开全局代理。
该方法设置后,Edge浏览器侧边栏New Bing无法正常使用,但网页版New Bing可以正常使用。
组件也叫构件。什么是组件?一个组件就是一个可以独立更换和升级的软件单元。组件具有如下特点:
1 能实现一定功能,或者提供一些服务。
2 不能单独运行,要作为系统的一部分来发挥作用。
3 是物理上的概念,不是逻辑上的概念。
4 可单独维护、可独立升级、可替换而不影响整个系统。
组件是物理上独立的一个东西,它可单独维护、升级、替换,画构件图的目的就是要做系统的构件设计,思考系统在物理上的划分,可利用现有哪些构件,哪些部分可做成构件供以后的项目重用等。
问题1:我们做软件设计时, 往往会提到 “模块 “ 这一词,“模块” 是不是构件呢?
不一定,每个人心中的“模块”的标准是不太一样的,有时候会按业务逻辑来划分模块,有时候从技术的角度来划分。模块只是为了方便说明问题,将软件人为地划分为几个部分而已,我们可以对照组件的上述几个特点来判断 “模块” 是不是构件。
问题2:软件常常会采用分层设计,那一层是一个构件吗?
大部分情况下分层设计中的每一层,仅是一个逻辑上的划分,物理上并不是单独的文件,这时这些分层不是组件。但具体要看实际的设计情况,可对照组件的上述几个特点来判断。
问题3:如何区分“服务”(service)和“组件”(component)?
所谓“组件”是指这样一个软件单元:它将被作者无法控制的其他应用程序使用,但后者不能对组件进行修改。也就是说,使用一个组件的应用程序不能修改组件的源代码,但可以通过作者预留的某种途径对其进行扩展,以改变组件的行为。
服务和组件有某种相似之处:它们都将被外部的应用程序使用。在我看来,两者之间最大的差异在于:组件是在本地使用的软件库(例如JAR文件、程序集、DLL、或者源码导入);而服务是进程外的组件,通过同步或异步的本机进程之间通信或远程接口调用(例如web service、消息系统、RPC,或者socket)这样的机制来被应用程序使用。
服务也可以调用其他服务,因为服务本身也是一个应用程序。
可以把一个个服务映射为一个个运行时的进程,但这仅仅是一个近似。一个服务也可以包括多个进程,比如一个服务应用程序的进程和仅被该服务使用的数据库进程。
服务可被独立部署。如果一个应用系统由在单个进程中的多个软件库所组成,那么对任一组件做一处修改,都不得不重新部署整个应用系统。但是如果该应用系统被分解为多个服务,那么对于一个服务的多处修改,仅需要重新部署这一个服务,例外是更改服务暴露出来的接口。
以服务的方式来实现组件化的另一个结果是,能获得更加显式的(explicit)组件接口。
服务的远程调用,比起进程内调用,远程调用更加昂贵。
参考
《微服务》 Martin Fowler
《IoC容器和依赖注入模式》 Martin Fowler
《火球 UML大战需求分析》
本文翻译自https://github.com/go-sql-driver/mysql/
Go-MySQL-Driver 用于Go语言database/sql包的MySQL驱动。

特性
database/sql包)sql.RawBytes支持LONG DATALOAD DATA LOCAL INFILE,也支持io.Readertime.Time解析要求
安装
使用shell中的go工具将包简单地安装到你的$GOPATH中:
$ go get -u github.com/go-sql-driver/mysql
确保Git安装在你的机器上和系统的PATH中。
用法
Go语言MySQL驱动是Go的database/sql/driver包的接口的实现。你只需要导入这个驱动,然后就可以使用完整的database/sql包的API。
使用mysql作为driverName,使用有效的DSN作为dataSourceName:
import (
"database/sql"
"time"
_ "github.com/go-sql-driver/mysql"
)
// ...
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
panic(err)
}
// 参见“重要设置”小节。
db.SetConnMaxLifetime(time.Minute * 3)
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(10)
重要设置
db.SetConnMaxLifetime()确保连接在MySQL服务器、操作系统或其他中间件关闭之前由驱动程序安全地关闭。由于某些中间件会在5分钟后关闭空闲连接,因此我们建议把超时时间设置为短于5分钟的时间。此设置也有助于负载平衡和更改系统变量。
db.SetMaxOpenConns()强烈建议使用该函数来限制应用程序使用的连接数。没有建议的限制数,因为它取决于具体应用程序和MySQL服务器。
db.SetMaxIdleConns()建议设置为与db.SetMaxOpenConns()的相同。当它小于SetMaxOpenConns()的设置时,连接打开和关闭的频率可能比你预期的要高得多。空闲连接可以通过db.SetConnMaxLifetime()设置超时时间。如果你想更快地关闭空闲连接,从Go 1.15开始,你可以使用db.SetConnMaxIdleTime()函数来设置。
DSN(Data Source Name,数据源名称)
数据源名称具有一个通用格式,例如PEAR DB所使用的,但没有类型前缀(用方括号标记可选部分):
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
完整形式的DSN:
username:password@protocol(address)/dbname?param=value
除了数据库名称之外,所有值都是可选的。因此,最小DSN为:
/dbname
如果不想预先选择一个数据库实例,请将dbname保留为空:
/
这与空DSN字符串的效果相同。
或者,我们也可以使用Config.FormatDSN通过填充一个结构体来创建DSN字符串。
密码
密码可以由任何字符组成,无需转义。
协议
有关可用网络的更多信息,请参阅net.Dial。一般来说,如果可以,你应该使用Unix域套接字,否则应该使用TCP,以获得最佳性能。
地址
对于TCP和UDP网络,地址的格式为host[:port]。如果省略port,将使用默认端口号。如果主机使用IPv6地址,则必须将其括在方括号中。函数net.JoinHostPort和net.SplitHostPort以这种形式处理地址。
对于Unix域套接字,地址是MySQL服务的套接字的绝对路径,例如:
/var/run/mysqld/mysqld.sock
或
/tmp/mysql.sock
参数
参数区分大小写!
请注意,true、TRUE、True或1中的任何一个表示布尔值“真”。毫不奇怪,布尔值“假”可以指定为以下任意值:false、FALSE、False或0。
allowAllFiles
Type: bool
Valid Values: true, false
Default: false
allowAllFiles=true禁用LOAD DATA LOCAL INFILE的文件白名单,并允许所有文件。可能不安全!
allowCleartextPasswords
Type: bool
Valid Values: true, false
Default: false
allowCleartextPasswords=true在帐户需要时允许使用明文(cleartext)客户端插件,例如使用PAM身份验证插件定义的插件。以明文形式发送密码在某些配置中可能是一个安全问题。如果密码有可能被拦截,为避免出现问题,客户端应使用保护密码的方法连接到MySQL服务器,包括使用TLS/SSL、IPsec或专用网络。
allowFallbackToPlaintext
Type: bool
Valid Values: true, false
Default: false
allowFallbackToPlaintext=true的作用类似于使用--ssl-mode=PREFERRED选项的MySQL客户端,如用于连接到服务器的命令选项中所述
allowNativePasswords
Type: bool
Valid Values: true, false
Default: true
allowNativePasswords=false禁止使用MySQL本机密码(native password)方式。
allowOldPasswords
Type: bool
Valid Values: true, false
Default: false
allowOldPasswords=true允许使用不安全的旧密码(old password)方式。应该避免使用这种方式,但在某些情况下是必要的。另请参阅old_passwords维基页面。
charset
Type: string
Valid Values: <name>
Default: none
设置用于客户端-服务器交互的字符集(“SET NAMES <value>“)。如果设置了多个字符集(用逗号分隔),则在设置当前字符集失败时继续尝试设置后续的字符集。例如, (charset=utf8mb4,utf8)设置utf8mb4(在MySQL 5.5.3中引入),如果是旧服务器就回退到utf8。
不鼓励使用charset参数,因为它会向服务器发出额外的查询。除非你需要回退行为,否则请改用collation参数。
checkConnLiveness
Type: bool
Valid Values: true, false
Default: true
在支持的平台上,Go从连接池中检索连接,在使用该连接之前会检查其是否活动。如果不活动,则将相应的连接标记为坏连接,并尝试使用另一个连接。checkConnLiveness=false不检查连接是否活动。
collation
Type: string
Valid Values: <name>
Default: utf8mb4_general_ci
设置连接时用于客户端-服务器交互的字符排序规则。与charset不同,collation不会发出额外的查询。如果指定的排序规则在目标服务器上不可用,则连接将失败。
可以使用SHOW COLLATION检索服务器的有效字符集列表。
MySQL 5.5支持默认排序规则(utf8mb4_general_ci)。对于较旧的MySQL,应该使用较旧的排序规则(例如utf8_general_ci)。 不能使用字符集”ucs2″、”utf16″、”utf16le”和”utf32″的排序规则(参考)。
clientFoundRows
Type: bool
Valid Values: true, false
Default: false
clientFoundRows=true会导致UPDATE语句返回匹配的行数,而不是更改的行数。
columnsWithAlias
Type: bool
Valid Values: true, false
Default: false
当columnsWithAlias=true时,调用sql.Rows.Columns()将返回表别名和用点分隔的列名。例如:
SELECT u.id FROM users as u
如果columnsWithAlias=true,将返回u.id而不仅仅是id。
interpolateParams
Type: bool
Valid Values: true, false
Default: false
如果interpolateParams=true,则调用db.Query()和db.Exec()时的占位符(?)将插入到具有给定参数的单个查询字符串中。这减少了往返次数,因为当interpolateParams=false时,驱动程序必须准备好一条语句,使用给定的参数执行它,然后再次关闭该语句。
这不能与多字节编码BIG5、CP932、GB2312、GBK或SJIS一起使用。这些都被拒绝,因为它们可能会引入SQL注入漏洞!
loc
Type: string
Valid Values: <escaped name>
Default: UTC
设置time.Time值的时区(当使用parseTime=true时)。如果时区设置为Local,那么设置为系统的时区。有关详细信息,请参阅time.LoadLocation。
注意,这将设置time.time值的时区,但不会更改MySQL的time_zone设置。为此,请参见time_zone系统变量,该变量也可以设置为DSN的参数。
请记住,参数值必须是url.QueryEscape编码的。或者,你可以手动将/替换为%2F。例如,US/Pacific(美国/太平洋时区)应该被设置为loc=US%2FPacific。
maxAllowedPacket
Type: decimal number
Default: 4194304
允许的最大数据包的大小(以字节为单位)。默认值为4MiB,应进行调整以匹配服务器设置。maxAllowedPacket=0可用于在每次连接时从服务器自动获取max_allowed_packet变量。
multiStatements
Type: bool
Valid Values: true, false
Default: false
是否允许在一个查询中使用多条语句。虽然这允许批量查询,但也大大增加了被SQL注入的风险。只返回第一个查询语句的结果,所有其他结果都被默默丢弃。当使用multiStatements时,?参数只能在第一条语句中使用。
parseTime
Type: bool
Valid Values: true, false
Default: false
parseTime=true将DATE和DATETIME值的输出类型更改为time.Time,而不是[]byte/string类型的日期或日期时间,例如0000-00-00 00:00:00将转换为time.Time的零值。
readTimeout
Type: duration
Default: 0
I/O读取的超时时间。该值必须是带有单位后缀的十进制数字(”ms”、”s”、”m”、”h”),例如”30s”、”0.5m”或”1m30s”。
rejectReadOnly
Type: bool
Valid Values: true, false
Default: false
rejectReadOnly=true会导致数据库驱动程序拒绝只读连接。这是针对自动故障切换期间可能出现的争用(race condition)情况,即故障切换后mysql客户端将连接到某个只读副本。
请注意,这应该是一种相当罕见的情况,因为自动故障转移通常发生在主服务器关闭时,并且竞态条件(race condition)不会发生,除非它在故障转移开始后立即恢复在线。另一方面,发生这种情况时,MySQL应用程序可能会卡在只读连接上,直到重新启动。然而,这种故障相当容易复现,例如在AWS Aurora的MySQL兼容集群上手动造成故障转移。
如果你不依赖只读事务来拒绝不应该发生的写入操作,那么在某些MySQL服务提供商(例如AWS Aurora)上设置此选项,对于故障切换来说更安全。
请注意,read-only服务器可能会返回1290号错误,此选项将导致重试该错误。但是,存在其他一些情况,也使用相同的1290错误号。因此启用此选项时,应确保应用程序在read-only模式之外,都不会导致1290号错误。
serverPubKey
Type: string
Valid Values: <name>
Default: none
可以使用mysql.RegisterServerPubKey注册服务器的公钥,然后可以在DSN中通过参数使用该公钥。公钥用于传输加密数据,例如用于身份验证。如果服务器的公钥是已知的,则应手动设置它,以避免每次需要它时都从服务器向客户端传输,代价昂贵且可能不安全。
timeout
Type: duration
Default: OS default
建立连接时的超时时间,又称拨号超时。该值必须是带单位后缀(”ms”、”s”、”m”、”h”)的十进制数,例如”30s”、”0.5m”或”1m30s”。
tls
Type: bool / string
Valid Values: true, false, skip-verify, preferred, <name>
Default: false
tls=true启用与服务器的TLS/SSL加密连接。如果要使用自签名或无效证书(服务器端),那么使用skip-verify参数;或通过preferred参数使用TLS(仅当得到服务器端建议时),这类似于skip-verify,但还允许回退到未加密的连接。skip-verify和preferred都不添加任何可靠的安全性。你可以在向mysql.RegisterTLSConfig注册后使用自定义的TLS配置。
writeTimeout
Type: duration
Default: 0
I/O写入超时。该值必须是带单位后缀(”ms”、”s”、”m”、”h”)的十进制数,例如”30s”、”0.5m”或”1m30s”。
系统变量
任何其他参数都被解释为系统变量:
<boolean_var>=<value>: SET <boolean_var>=<value><enum_var>=<value>: SET <enum_var>=<value><string_var>=%27<value>%27: SET <string_var>='<value>'规则:
url.QueryEscape编码过(这意味着字符串变量的值必须用%27包裹)。例如:
autocommit=1: SET autocommit=1time_zone=%27Europe%2FParis%27: SET time_zone='Europe/Paris'transaction_isolation=%27REPEATABLE-READ%27: SET transaction_isolation='REPEATABLE-READ'实例
user@unix(/path/to/socket)/dbname
root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local
user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true
通过设置系统变量sql_mode将警告视为错误:
user:password@/dbname?sql_mode=TRADITIONAL
通过IPv6的TCP:
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s&collation=utf8mb4_unicode_ci
远程主机上的TCP,例如Amazon RDS:
id:password@tcp(your-amazonaws-uri.com:3306)/dbname
应用引擎上的谷歌云SQL:
user:password@unix(/cloudsql/project-id:region-name:instance-name)/dbname
使用本地主机上默认端口(3306)的TCP:
user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped
使用默认协议(tcp)和主机(localhost:3306):
user:password@/dbname
未预先选择数据库:
user:password@/
连接池和超时
连接池由Go的database/sql包管理。有关如何配置池的大小以及连接在池中停留的时间的详细信息,请参阅database/sql文档中的*DB.SetMaxOpenConns、*DB.Set MaxIdleConns和*DB.Set-ConnMaxLifetime。每个单独连接的读取、写入和拨号超时分别使用DSN参数readTimeout、writeTimeout和timeout进行配置。
支持context.Context
此驱动程序支持Go 1.8中引入的ColumnType接口,但ColumnType.Length()除外,该接口目前不受支持。所有无符号的数据库类型都将返回带有Unsigned的INT、TINYINT、SMALLINT或BIGINT名称。
支持ColumnType
此驱动程序支持Go 1.8中引入的ColumnType接口,但ColumnType.Length()除外,该接口目前不受支持。所有无符号的数据库类型都将返回带有Unsigned的INT、TINYINT、SMALLINT或BIGINT名称。
支持context.Context
在Go 1.8,database/sql包添加了对context.Context的支持。此驱动程序支持查询超时和通过context取消语句的执行。有关详细信息,请参阅database/sql包中的context支持。
支持LOAD DATA LOCAL INFILE
对于此功能,你需要直接访问软件包。因此,你必须更改导入路径(无_):
import "github.com/go-sql-driver/mysql"
文件必须通过mysql.RegisterLocalFile(filepath)注册来明确允许加载里面的数据(推荐使用这种做法),或者必须通过使用DSN参数allowAllFiles=true来禁用allowlist检查(这种做法可能不安全!)。
要使用io.Reader,必须使用mysql.RegisterReaderHandler(name, handler)注册处理函数,该函数返回io.Reador或io.ReadCloser。然后,Reader可以使用文件路径Reader::<name>。可以为不同的处理程序 handler注册不同的名字name,,当你不再需要它时,使用DeregisterReaderHandler反注册。
有关详细信息,请参阅Go MySQL驱动程序的godoc。
支持time.Time
MySQL DATE和DATETIME值的默认内部输出类型为[]byte,这允许你将值扫描到程序中的[]byte、string或sql.RawBytes变量中。
然而,许多人希望将MySQL的DATE和DATETIME值扫描到Go语言的time.Time变量中,这在逻辑上等同于MySQL中的DATE和DATETIME值。你可以通过使用DSN参数parseTime=true将内部输出类型从[]byte更改为time.Time。你可以使用DSN参数loc设置默认time.Time的时区位置。
注意:截至Go 1.1,这使得time.Time成为唯一可以扫描DATE和DATETIME值的变量类型。这会破坏例如对sql.RawBytes的支持。
支持Unicode
由于1.5版本的Go MySQL驱动程序默认情况下自动使用排序规则utf8mb4_general_ci。
可以使用DSN参数collation设置其他排序规则/字符集。
1.0版的驱动程序建议将&charset=utf8(即MySQL命令SET NAMES utf8的别名)添加到DSN中,以启用正确的UTF-8支持。但现在已经没有必要这么做了,如果要设置其他排序规则/字符集的话,应该首选collation参数。
有关MySQL的Unicode支持的更多详细信息参见http://dev.mysql.com/doc/refman/8.0/en/charset-unicode.html。
测试/开发
要运行驱动程序测试,你可能需要调整配置。有关详细信息,请参阅测试的Wiki 页面。
Go语言的MySQL驱动程序的功能还不完善。如果你想做出贡献,我们将非常感谢你的帮助,你可以处理未解决的问题或查看拉取请求。
有关详细信息,请参阅贡献指南。
License
Go语言的MySQL驱动程序在Mozilla公共许可证2.0版(Mozilla Public License Version 2.0)下获得许可。
Mozilla将许可范围总结如下:
MPL:许可证适用于任何包含MPL代码的文件。
这意味着:
如果你对许可证有更多疑问,请阅读MPL 2.0的常见问题解答。
你可以在此处阅读完整条款:LICENSE。
本文翻译自https://github.com/go-sql-driver/mysql/wiki
2023/03/05
database/sql包的详细介绍可以在这里找到:http://go-database-sql.org/
浅谈sql.Open函数
首先,你应该了解sql.DB不是一个连接。当你使用sql.Open()时,你将获得数据库的句柄。database/sql包在后台管理一个连接池,并且在你需要它们之前不会打开任何连接。因此sql.Open()不直接打开连接。因此,如果服务器不可用或连接数据的(用户名,密码)不正确,sql.Open()不会返回一个错误。如果你想在进行查询之前检查是否成功连接到数据库(例如在应用程序启动时),你可以使用db.Ping()。
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
panic(err.Error()) // 此处只是示例,在实际开发中你应该使用合适的错误处理函数,而不是使用panic
}
defer db.Close()
// 上面的Open函数不真的打开一个连接。可以使用以下函数来验证连接字符串是否能成功连接到数据库
err = db.Ping()
if err != nil {
panic(err.Error()) // 在实际开发中不要使用painc,而是使用合适的错误处理函数
}
// 以下省略执行查询语句等代码
[...]
预处理语句
假设一个具有以下结构的空表,表名是squareNum:
+--------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------+------+-----+---------+-------+
| number | int(11) | NO | PRI | NULL | |
| squareNumber | int(11) | NO | | NULL | |
+--------------+---------+------+-----+---------+-------+
在此示例中,我们准备了两条语句,一条用于插入元组(行),一条用于查询。
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:password@/database")
if err != nil {
panic(err.Error())
}
defer db.Close()
// 用于插入数据的预处理语句
stmtIns, err := db.Prepare("INSERT INTO squareNum VALUES( ?, ? )") // ?号是占位符
if err != nil {
panic(err.Error())
}
defer stmtIns.Close() // 当main函数快要运行完毕(程序终止)时,关闭预处理语句,释放占用的资源
// 用于查询数据的预处理语句
stmtOut, err := db.Prepare("SELECT squareNumber FROM squarenum WHERE number = ?")
if err != nil {
panic(err.Error())
}
defer stmtOut.Close()
// 插入0到24数字到数据库
for i := 0; i < 25; i++ {
_, err = stmtIns.Exec(i, (i * i)) // 插入元组(i, i^2)
if err != nil {
panic(err.Error())
}
}
var squareNum int // 我们在此处浏览结果
// 查询13的平方数
err = stmtOut.QueryRow(13).Scan(&squareNum) // WHERE number = 13
if err != nil {
panic(err.Error())
}
fmt.Printf("The square number of 13 is: %d", squareNum)
// 查询1的平方数试试
err = stmtOut.QueryRow(1).Scan(&squareNum) // WHERE number = 1
if err != nil {
panic(err.Error())
}
fmt.Printf("The square number of 1 is: %d", squareNum)
}
忽略NULL值
也许你已经遇到了这个错误:sql: Scan error on column index 1: unsupported driver -> Scan pair: <nil> -> *string
通常在这种情况下你会使用sql.NullString。但有时你并不关心该值是否为NULL,你只想将其视为一个空字符串。
你可以使用一个变通方法来做到这一点,这利用了一个事实,即nil []byte被转换为空字符串。你可以简单地使用*[]byte(或*sql.RawBytes),而不是使用*string作为rows.Scan(...)的目标,因为它们可以接收nil值:
[...]
var col1, col2 []byte
for rows.Next() {
// 用[]byte类型的变量作为Scan函数的出参
err = rows.Scan(&col1, &col2)
if err != nil {
panic(err.Error())
}
// 再转换为字符串,用于输出
fmt.Println(string(col1), string(col2))
}
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 打开数据库连接
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
panic(err.Error())
}
defer db.Close()
// 执行查询
rows, err := db.Query("SELECT * FROM table")
if err != nil {
panic(err.Error())
}
// 获取列名
columns, err := rows.Columns()
if err != nil {
panic(err.Error())
}
// 创建一个切片用于存储一行数据
values := make([]sql.RawBytes, len(columns))
// rows.Scan想要'[]interface{}'作为一个参数, 所以我们必须把值复制到这样一个切片里。更多细节参考http://code.google.com/p/go-wiki/wiki/InterfaceSlice
scanArgs := make([]interface{}, len(values))
for i := range values {
scanArgs[i] = &values[i]
}
// 获取行数据
for rows.Next() {
// 从数据中获取RawBytes
err = rows.Scan(scanArgs...)
if err != nil {
panic(err.Error())
}
// 现在使用获取到的数据做点事情,此处我们仅仅输出每列的值
var value string
for i, col := range values {
// 这里我们可以检查值是否是nil(在数据库里就是NULL值)
if col == nil {
value = "NULL"
} else {
value = string(col) // 把列值转换为字符串用来输出
}
fmt.Println(columns[i], ": ", value)
}
fmt.Println("-----------------------------------")
}
// 检查在获取行数据的过程中,是否遇到了一个错误
if err = rows.Err(); err != nil {
panic(err.Error())
}
}
旧密码(old_passwords)
什么是old_passwords?
MySQL 4.1版(2004年发布!)更改了协议,引入了更安全的密码认证。添加了变量old_password,它启用旧密码认证这种传统的认证方式,但禁用新的更安全的密码认证方式。旧密码认证方式使用非常弱的哈希,这就是为什么它被认为是不安全的。如果你不需要传统的旧密码认证方式,就不应该使用它!
由于旧密码不安全且已被弃用,因此Go语言的MySQL驱动程序在默认情况下不启用此认证方式。如果你依赖它,可以通过在DSN中添加allowOldPasswords=true来显式启用它。
如何禁用它?
在MySQL的配置文件my.cnf(Windows上为my.ini)中将old_passwords设置为false。在Linux上,你可以在/etc/my.cnf找到此文件。
变量old_passwords属于mysqld小节,如果在配置文件里找不到它,就添加它:
[mysqld]
old_passwords = 0
你可能还需要重新生成密码。有关如何升级,参考http://code.openark.org/blog/mysql/upgrading-passwords-from-old_passwords-to-new-passwords。
测试(Testing)
也许你需要编辑已经打开的数据库连接的参数。以下是如何设置不同密码的示例:
Linux/Unix
$ export MYSQL_TEST_PASS=root
Windows
$ SET MYSQL_TEST_PASS=root
参数列表
MYSQL_TEST_USER ( User用户名 )
MYSQL_TEST_PASS ( Password 密码)
MYSQL_TEST_PROT ( Network Protocol 网络协议)
MYSQL_TEST_ADDR (网络地址,IPv4或IPv6)
MYSQL_TEST_DBNAME (数据库名称)
MYSQL_TEST_CONCURRENT ( 值为1启用并发测试)
开发思路
在Auth响应中发送连接参数[MySQL 5.6.6+]
http://dev.mysql.com/doc/internals/en/capability-flags.html#flag-CLIENT_CONNECT_ATTRS
支持更多认证插件
网络压缩模式
http://dev.mysql.com/doc/internals/en/compression.html
使用分离的goroutine从连接中读取
目前,我们发送请求数据包,然后接收响应数据包。
由于Go没有提供有效的非阻塞EOF检查方法,即使服务器已经关闭连接,我们也可能会发送请求数据包。在已关闭的连接上发送请求会使ErrBadConn不安全。 如果我们可以在发送数据包之前检测到EOF,我们就可以安全地使用ErrBadConn。
使用分离的goroutine从连接中读取数据是Go的常规编程方式。读数据的goroutine 也可能需要实现对Context支持。
可能会有显着的性能衰退。但我认为我们应该这样做。