执行不返回行数据的SQL语句

本文翻译自《Executing SQL statements that don’t return data》。

执行不返回数据的数据库操作时,请使用database/sql包中的ExecExecContext方法。以这种方式执行的SQL语句包括INSERTDELETEUPDATE

当查询可能返回行数据时,请改QueryQueryContext方法。有关详细信息,请参阅查询一个数据库

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语句的函数

DB.Exec

DB.ExecContext

孤立地执行单个SQL语句。

Tx.Exec

Tx.ExecContext

在较大的事务中执行SQL语句。有关详细信息,请参阅执行事务

Stmt.Exec

Stmt.ExecContext

执行已准备好的SQL语句。有关详细信息,请参阅使用准备好的语句

Conn.ExecContext

用于专用的连接。有关详细信息,请参阅管理连接

Go语言MySQL驱动程序GitHub README.md中文翻译

本文翻译自https://github.com/go-sql-driver/mysql/

Go-MySQL-Driver 用于Go语言database/sql包的MySQL驱动。

特性

要求

安装

用法

       DSN(Data Source Name,数据源名称)

              密码

              协议

              地址

              参数

              实例

       连接池和超时

       支持context.Context

       支持ColumnType

       支持LOAD DATA LOCAL INFILE

       支持time.Time

       支持Unicode

测试/开发

License

特性

  • 轻量又快速
  • 纯Go语言实现,并非是对C语言的封装
  • 通过TCP/IPv4、TCP/IPv6、Unix域套接字或自定义协议进行连接
  • 自动处理断开的连接
  • 自动连接池(通过database/sql包)
  • 支持大于16MB的查询字符串
  • 完整的sql.RawBytes支持
  • 智能地处理预处理语句中的LONG DATA
  • 通过文件白名单安全地支持LOAD DATA LOCAL INFILE,也支持io.Reader
  • 可选的time.Time解析
  • 可选的占位符插值

要求

  • Go 1.13或更高版本。我们的目标是支持Go的3个最新版本。
  • MySQL (4.1+)、MariaDB、Percona Server、Google CloudSQL或Sphinx (2.2.3+)

安装

使用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)

案例见我们的wiki

重要设置

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&...&paramN=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.JoinHostPortnet.SplitHostPort以这种形式处理地址。

对于Unix域套接字,地址是MySQL服务的套接字的绝对路径,例如:

/var/run/mysqld/mysqld.sock

/tmp/mysql.sock

参数

参数区分大小写!

请注意,trueTRUETrue1中的任何一个表示布尔值“真”。毫不奇怪,布尔值“假”可以指定为以下任意值:falseFALSEFalse0

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时,驱动程序必须准备好一条语句,使用给定的参数执行它,然后再次关闭该语句。

这不能与多字节编码BIG5CP932GB2312GBKSJIS一起使用。这些都被拒绝,因为它们可能会引入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=trueDATEDATETIME值的输出类型更改为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-verifypreferred都不添加任何可靠的安全性。你可以在向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=1
  • time_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参数readTimeoutwriteTimeouttimeout进行配置。

支持context.Context

此驱动程序支持Go 1.8中引入的ColumnType接口,但ColumnType.Length()除外,该接口目前不受支持。所有无符号的数据库类型都将返回带有UnsignedINTTINYINTSMALLINTBIGINT名称。

支持ColumnType

此驱动程序支持Go 1.8中引入的ColumnType接口,但ColumnType.Length()除外,该接口目前不受支持。所有无符号的数据库类型都将返回带有UnsignedINTTINYINTSMALLINTBIGINT名称。

支持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.Readorio.ReadCloser。然后,Reader可以使用文件路径Reader::<name>。可以为不同的处理程序 handler注册不同的名字name,,当你不再需要它时,使用DeregisterReaderHandler反注册。

有关详细信息,请参阅Go MySQL驱动程序的godoc

支持time.Time

MySQL DATEDATETIME值的默认内部输出类型为[]byte,这允许你将值扫描到程序中的[]bytestringsql.RawBytes变量中。

然而,许多人希望将MySQL的DATEDATETIME值扫描到Go语言的time.Time变量中,这在逻辑上等同于MySQL中的DATEDATETIME值。你可以通过使用DSN参数parseTime=true将内部输出类型从[]byte更改为time.Time。你可以使用DSN参数loc设置默认time.Time的时区位置

注意:截至Go 1.1,这使得time.Time成为唯一可以扫描DATEDATETIME值的变量类型。这会破坏例如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代码的文件

这意味着:

  • 你可以在私人和商业用途中使用未更改的源代码。
  • 分发时,你必须根据a) MPL 2.0本身或b)兼容的许可证(例如GPL 3.0或Apache License 2.0)发布MPL 2.0许可的任何更改了源代码的文件
  • 只要根据MPL 2.0许可的文件未更改,你就无需发布你的库的源代码。

如果你对许可证有更多疑问,请阅读MPL 2.0的常见问题解答

你可以在此处阅读完整条款:LICENSE

Go语言MySQL驱动程序GitHub wiki中文翻译

本文翻译自https://github.com/go-sql-driver/mysql/wiki

2023/03/05

例子

旧密码(old_passwords)

测试(Testing)

开发思路

例子

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

RawBytes

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/connection-phase-packets.html#packet-Protocol::HandshakeResponse41

http://dev.mysql.com/doc/internals/en/capability-flags.html#flag-CLIENT_CONNECT_ATTRS

支持更多认证插件

  • .mylogin.cnf http://dev.mysql.com/doc/refman/5.6/en/mysql-nutshell.html
  • SHA-256 http://dev.mysql.com/doc/refman/5.6/en/sha256-authentication-plugin.html
  • auth_socket http://dev.mysql.com/doc/refman/5.6/en/socket-authentication-plugin.html

网络压缩模式

http://dev.mysql.com/doc/internals/en/compression.html

使用分离的goroutine从连接中读取

目前,我们发送请求数据包,然后接收响应数据包。

由于Go没有提供有效的非阻塞EOF检查方法,即使服务器已经关闭连接,我们也可能会发送请求数据包。在已关闭的连接上发送请求会使ErrBadConn不安全。 如果我们可以在发送数据包之前检测到EOF,我们就可以安全地使用ErrBadConn

使用分离的goroutine从连接中读取数据是Go的常规编程方式。读数据的goroutine 也可能需要实现对Context支持。

可能会有显着的性能衰退。但我认为我们应该这样做。

教程:访问关系型数据库

本文翻译自《Tutorial: Accessing a relational database》。

目录

先决条件

为你的代码创建一个文件夹

建立一个数据库

查找并导入数据库的驱动程序

获取数据库句柄并连接

查询多行

查询单行

添加数据

结论

完成全部代码

本教程介绍了使用Go及其标准库中的database/sql包访问关系数据库的基础知识。

如果你对Go及其工具有基本的了解,你将充分利用本教程。如果这是你第一次接触Go,请先参阅教程:Go入门以获得快速介绍。

你将使用的database/sql包包括用于连接数据库、执行事务、取消正在进行的操作等的类型和函数。有关使用该包的更多详细信息,请参阅访问关系型数据库

在本教程中,你将创建一个数据库,然后编写代码来访问该数据库。你的示例项目将是有关古典爵士乐唱片的数据存储库。

在本教程中,你将逐步完成以下部分:

1 为你的代码创建一个文件夹。

2 建立一个数据库。

3 导入数据库驱动。

4 获取数据库句柄并连接。

5 查询多行。

6 查询单行。

7 添加数据。

注意:有关其他教程,请参阅教程

先决条件

  • 安装MySQL关系数据库管理系统 (DBMS)。
  • 安装Go语言开发环境。有关安装说明,请参阅安装Go
  • 一个编辑代码的工具。你拥有的任何文本编辑器都可以正常工作。
  • 一个命令行终端。Go在Linux和Mac中的任何终端,以及Windows中的PowerShell或cmd上都能很好地工作。

为你的代码创建一个文件夹

首先,为你要编写的代码创建一个文件夹。

1 打开命令提示符并切换到你的家目录。 在Linux或Mac上:

$ cd

在Windows上:

C:\> cd %HOMEPATH%

对于本教程的其余部分,我们将显示$作为提示符。我们使用的命令也适用于 Windows。

2 在命令提示符下,为你的代码创建一个名为data-access的目录。

$ mkdir data-access
$ cd data-access

3 创建一个模块,你可以在其中管理将在本教程中添加的依赖项。

运行go mod init命令,为它提供新代码的模块路径。

$ go mod init example/data-access
go: creating new go.mod: module example/data-access

此命令创建一个go.mod文件,其中将列出你添加的依赖项以供追踪。有关更多信息,请参阅管理依赖项

注意:在实际开发中,你应该指定一个更符合你自己需求的模块路径。有关更多信息,请参阅管理依赖项

接下来,你将创建一个数据库。

建立一个数据库

在此步骤中,你将创建要使用的数据库。你将使用DBMS本身的CLI来创建数据库和表,以及添加数据。

你将创建一个数据库,其中包含有关黑胶唱片上的老式爵士乐唱片的数据。

此处的代码使用MySQL CLI,大多数DBMS都有自己的具有类似功能的CLI。

1 打开新的命令提示符。

2 在命令行中,登录DBMS,如下面的MySQL示例所示。

$ mysql -u root -p
Enter password:

mysql>

3 在mysql命令提示符下,创建一个数据库。

mysql> create database recordings;

4 切换到你刚刚创建的数据库,以便你可以添加表。

mysql> use recordings;
Database changed

5 在文本编辑器的数据访问文件夹中,创建一个名为create-tables.sql的文件来保存用于添加表的SQL脚本。

6 在文件中,粘贴以下SQL代码,然后保存文件。

DROP TABLE IF EXISTS album;
CREATE TABLE album (
  id         INT AUTO_INCREMENT NOT NULL,
  title      VARCHAR(128) NOT NULL,
  artist     VARCHAR(255) NOT NULL,
  price      DECIMAL(5,2) NOT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO album
  (title, artist, price)
VALUES
  ('Blue Train', 'John Coltrane', 56.99),
  ('Giant Steps', 'John Coltrane', 63.99),
  ('Jeru', 'Gerry Mulligan', 17.99),
  ('Sarah Vaughan', 'Sarah Vaughan', 34.98);

在此SQL代码中,你:

  • 删除一个名为album的表。如果你想重新开始使用该表,执行此命令即可。
  • 创建一个包含四列的album表:titleartistprice。每行的id值由DBMS自动创建。
  • 添加四行值。

7 在mysql命令提示符下,运行你刚刚创建的脚本。

使用以下形式的source命令:

mysql> source /path/to/create-tables.sql

8 使用SELECT语句来验证你是否成功创建包含数据的表。

mysql> select * from album;
+----+---------------+----------------+-------+
| id | title         | artist         | price |
+----+---------------+----------------+-------+
|  1 | Blue Train    | John Coltrane  | 56.99 |
|  2 | Giant Steps   | John Coltrane  | 63.99 |
|  3 | Jeru          | Gerry Mulligan | 17.99 |
|  4 | Sarah Vaughan | Sarah Vaughan  | 34.98 |
+----+---------------+----------------+-------+
4 rows in set (0.00 sec)

接下来,你将编写一些Go代码来连接数据库以便你可以查询数据。

查找并导入数据库的驱动程序

现在你已经有了一个包含一些数据的数据库,开始写你的Go代码。

找到并导入一个数据库驱动程序,该驱动程序会将你通过database/sql包中的函数发出的请求转换为数据库可以理解的请求。

1 在你的浏览器中,访问SQLDrivers wiki页面以确定你可以使用的驱动程序。

使用页面上的列表来确定你将使用的驱动程序。为了在本教程中访问MySQL,你将使用Go-MySQL-Driver

2 请注意驱动程序的包名称,此处为github.com/go-sql-driver/mysql

3 使用你的文本编辑器,创建一个用于编写你的Go代码的文件,命名为main.go保存在你之前创建的数据访问目录中。

4 进入main.go,粘贴以下代码导入驱动包。

package main

import "github.com/go-sql-driver/mysql"

在此代码中,你:

  • 将你的代码添加到main包中,以便可以独立执行它。
  • 导入MySQL驱动程序github.com/go-sql-driver/mysql

导入驱动程序后,你将开始编写代码来访问数据库。

获取数据库句柄并连接

现在编写一些Go代码,使用数据库句柄(handler)访问数据库。

你将使用指向sql.DB结构体的指针,该结构表示对特定数据库的访问。

编写代码

1 在main.go中,在你刚刚添加的import代码下面,粘贴以下go代码以创建数据库句柄。

var db *sql.DB

func main() {
    // 设置连接参数。
    cfg := mysql.Config{
        User:   os.Getenv("DBUSER"),
        Passwd: os.Getenv("DBPASS"),
        Net:    "tcp",
        Addr:   "127.0.0.1:3306",
        DBName: "recordings",
    }
    // 获取一个数据库句柄。
    var err error
    db, err = sql.Open("mysql", cfg.FormatDSN())
    if err != nil {
        log.Fatal(err)
    }

    pingErr := db.Ping()
    if pingErr != nil {
        log.Fatal(pingErr)
    }
    fmt.Println("Connected!")
}

在这段代码中:

  • 声明一个类型为*sql.DB的数据库变量db。这就是你的数据库句柄。

db作为全局变量简化了这个例子。在生产环境中,你应该避免使用全局变量,应该将变量传递给需要它的函数,或者将其封装在结构体中。

  • 使用MySQL驱动程序的Config(配置)类型和这个类型的FormatDSN函数来收集连接选项,并将其格式化为连接字符串的DSN。

Config结构体使得代码比直接使用连接字符串DSN更具可读性。

  • 调用sql.Open初始化db变量,并传入FormatDSN函数的返回值。
  • 检查sql.Open返回的错误变量。例如,如果数据库连接格式不正确,它可能会连接失败。

为了简化代码,我们调用log.Fatal函数来结束执行并将错误打印到控制台。在生产环境中,你应该以更优雅的方式来处理错误。

  • 调用DB.Ping函数以确认是否正常连接到数据库。在运行时,sql.Open可能不会立即连接到数据库,具体取决于驱动程序。你在这里使用DB.Ping函数来确认database/sql包可以在需要时连接到数据库。
  • 检查Ping函数是否返回错误,以防连接失败。
  • 如果Ping函数连接成功,则打印消息。

2. 在main.go文件的顶部附近,就在包声明的下方,导入支持你刚刚编写的代码的包。

main.go文件的顶部现在应该是这样的:

package main

import (
    "database/sql"
    "fmt"
    "log"
    "os"

    "github.com/go-sql-driver/mysql"
)

3. 保存main.go文件

运行这段代码

1. 开始将MySQL驱动程序模块作为一个依赖项进行跟踪。

使用go get添加github.com/go-sql-driver/mysql模块作为你自己模块的一个依赖项。使用句点参数表示“获取当前目录中的代码的所有依赖项”。

$ go get .
go get: added github.com/go-sql-driver/mysql v1.6.0

Go下载了这个依赖项,因为你在上一步中将它添加到了import声明中。有关跟踪依赖项的详细信息,请参阅添加一个依赖项

在命令提示符下,设置DBUSERDBPASS环境变量以供这个Go程序使用。

在Linux或Mac上:

$ export DBUSER=username
$ export DBPASS=password

在Windows上:

C:\Users\you\data-access> set DBUSER=username
C:\Users\you\data-access> set DBPASS=password

3. 在包含main.go的目录中的命令行中,通过键入go run和一个点参数来运行代码,意思是“运行当前目录中的main包”:

$ go run .
Connected!

你可以连接成功!接下来,你将查询一些数据。

查询多行

在本节中,你将使用Go执行一个SQL查询,该查询旨在返回多行。

对于可能返回多行的SQL语句,可以使用database/sql包中的Query方法,然后循环获取它返回的行。(稍后将在查询单行一节中学习如何查询单行。)

编写代码

1. 进入main.go,在func main的正上方,粘贴Album结构体的以下定义。你将使用它来保存从查询返回的行数据。

type Album struct {
    ID     int64
    Title  string
    Artist string
    Price  float32
}

2. 在func main下,粘贴以下albumsByArtist函数以查询数据库:

// albumsByArtist函数查询具有指定艺术家(Artist)姓名的专辑。
func albumsByArtist(name string) ([]Album, error) {
    // 一个切片用于保存返回行数据的相册(Album)信息。
    var albums []Album

    rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
    if err != nil {
        return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    }
    defer rows.Close()
    // 遍历每一行,使用Scan函数将列数据分配给结构体的对应字段。
    for rows.Next() {
        var alb Album
        if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
            return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
        }
        albums = append(albums, alb)
    }
    if err := rows.Err(); err != nil {
        return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    }
    return albums, nil
}

在此代码中,你:

  • 声明Album类型的一个album切片,用来存储来自返回行的数据。结构体字段名称和类型对应于数据库列的名称和类型。
  • 使用DB.Query执行SELECT语句以查询具有指定艺术家(Artist)姓名的专辑。

查询的第一个参数是SQL语句。在该参数之后,可以传递零个或多个任意类型的参数。这些参数的值将依次填入你在SQL语句中使用?号占据的位置。通过将SQL语句与参数值分开(而不是将它们用fmt.Sprintf函数连接成一个字符串),你可以让database/sql包将参数值与SQL文本分开发送,从而消除任何SQL注入风险。

  • defer rows.Close()延迟关闭rows,以便在函数退出时释放它持有的任何资源。
  • 遍历返回的行,使用Rows.Scan将每行的列值分别分配给Album结构体字段。

Scan函数获取指向Go变量的指针列表,对应的列值将写入其中。在这里,你传入&运算符加alb结构体变量中的字段。通过这些指针Scan函数写入以更新结构体的字段。

  • 在循环体内,也检查Scan函数是否有错误返回。
  • 在循环体内,将新的alb变量附加到albums切片。
  • 在循环体之后,使用rows.Err函数检查整个查询是否有错误发生。请注意,如果查询本身失败了,则检查此处的错误是发现结果不完整的唯一方法。

3. 更新你的main函数以调用albumsByArtist函数。

func main的末尾,添加以下代码:

albums, err := albumsByArtist("John Coltrane")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Albums found: %v\n", albums)

在新代码中,你现在:

  • 调用albumsByArtist函数,将其返回值分配给新的albums变量。
  • 打印结果。

运行代码

从包含main.go文件的目录的命令行运行代码:

$ go run .
Connected!
Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]

接下来你将查询单行。

查询单行

在本节中,你将使用Go查询数据库中的一行。

对于你知道的最多返回一行数据的SQL语句,你可以使用QueryRow函数,它比使用Query函数更简单。

写出代码

1. 在albumsByArtist函数下,粘贴以下albumByID函数的代码:

// albumByID函数使用指定ID来查询相册(album)信息。
func albumByID(id int64) (Album, error) {
    // 声明一个用来存储返回行数据的Album结构体变量。
    var alb Album

    row := db.QueryRow("SELECT * FROM album WHERE id = ?", id)
    if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
        if err == sql.ErrNoRows {
            return alb, fmt.Errorf("albumsById %d: no such album", id)
        }
        return alb, fmt.Errorf("albumsById %d: %v", id, err)
    }
    return alb, nil
}

在这段代码中,你:

  • 使用DB.QueryRow执行SELECT语句来查询具有指定ID的相册的信息。

它返回一个sql.Row。为了简化你的代码,QueryRow函数不会返回错误。相反,它安排稍后的Rows.Scan函数返回任何可能发生的查询错误(例如sql.ErrNoRows)。

  • 使用Row.Scan函数将列值复制到结构体字段中。
  • 检查Scan函数返回错误。

特殊错误sql.ErrNoRows表示查询未返回任何行。通常,该错误值得用更具体的文本来说明,例如此处的“没有这样的专辑(no such album)”。

2. 更新main函数以调用albumByID函数。

func main的末尾,添加以下代码:

// 在此处硬编码ID为2以测试查询。
alb, err := albumByID(2)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Album found: %v\n", alb)

在这段代码中,你现在:

  • 调用你添加的albumByID函数。
  • 打印返回的相册的ID。

运行代码

在包含main.go的目录的命令行运行代码:

$ go run .
Connected!
Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
Album found: {2 Giant Steps John Coltrane 63.99}

接下来,你将向数据库添加一条相册信息数据。

添加数据

在本节中,你将使用Go语言执行SQL的INSERT语句以向数据库添加新行。

你已经了解了如何将QueryQueryRow函数与返回数据的SQL语句一起使用。如果要执行不返回数据的SQL语句,你可以使用Exec函数。

写代码

1. 在albumByID函数下方,粘贴以下addAlbum函数以在数据库中插入新专辑的数据:

// addAlbum函数添加指定专辑的数据到数据库,返回这条新记录的ID
func addAlbum(alb Album) (int64, error) {
    result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
    if err != nil {
        return 0, fmt.Errorf("addAlbum: %v", err)
    }
    id, err := result.LastInsertId()
    if err != nil {
        return 0, fmt.Errorf("addAlbum: %v", err)
    }
    return id, nil
}

在此代码中,你:

Query函数一样,Exec函数接受SQL语句,后跟SQL语句的参数值。

  • 检查尝试INSERT时是否有错误发生。
  • 使用Result.LastInsertId函数检索插入到数据库的新行的ID。
  • 检查尝试检索ID时是否有错误发生。

2. 更新main函数以调用新的addAlbum函数。

func main的末尾,添加以下代码:

albID, err := addAlbum(Album{
    Title:  "The Modern Sound of Betty Carter",
    Artist: "Betty Carter",
    Price:  49.99,
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("ID of added album: %v\n", albID)

在新代码中,你现在:

  • 调用addAlbum函数,传入新的专辑数据,然后把添加的这行专辑数据的ID返回给albID变量。

运行代码

在包含main.go的目录的命令行运行代码:

$ go run .
Connected!
Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
Album found: {2 Giant Steps John Coltrane 63.99}
ID of added album: 5

结论

恭喜你刚刚使用Go对关系数据库执行了简单的操作。

建议你进行下一个主题:

  • 看看数据访问指南,其中包括更多关于此处仅涉及的主题的信息。
  • 如果你是Go语言新手,你可以在Effective GoHow to write Go code中找到有用的最佳实践。
  • Go Tour是对Go基础知识的一个很好的循序渐进的介绍。

完成全部代码

本节给出本教程的全部代码:

package main

import (
    "database/sql"
    "fmt"
    "log"
    "os"

    "github.com/go-sql-driver/mysql"
)

var db *sql.DB

type Album struct {
    ID     int64
    Title  string
    Artist string
    Price  float32
}

func main() {
    // 设置连接参数
    cfg := mysql.Config{
        User:   os.Getenv("DBUSER"),
        Passwd: os.Getenv("DBPASS"),
        Net:    "tcp",
        Addr:   "127.0.0.1:3306",
        DBName: "recordings",
    }
    // 获取一个数据库句柄
    var err error
    db, err = sql.Open("mysql", cfg.FormatDSN())
    if err != nil {
        log.Fatal(err)
    }

    pingErr := db.Ping()
    if pingErr != nil {
        log.Fatal(pingErr)
    }
    fmt.Println("Connected!")

    albums, err := albumsByArtist("John Coltrane")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Albums found: %v\n", albums)

    // 获取ID为2的相册的信息,测试查询语句
    alb, err := albumByID(2)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Album found: %v\n", alb)

    albID, err := addAlbum(Album{
        Title:  "The Modern Sound of Betty Carter",
        Artist: "Betty Carter",
        Price:  49.99,
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("ID of added album: %v\n", albID)
}

// albumsByArtist函数查询具有指定艺术家(Artist)姓名的专辑。
func albumsByArtist(name string) ([]Album, error) {
    // 一个切片用于保存返回行数据的相册(Album)信息。
    var albums []Album

    rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
    if err != nil {
        return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    }
    defer rows.Close()
    // 遍历每一行,使用Scan函数将列数据分配给结构体的对应字段。
    for rows.Next() {
        var alb Album
        if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
            return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
        }
        albums = append(albums, alb)
    }
    if err := rows.Err(); err != nil {
        return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    }
    return albums, nil
}

// albumByID函数使用指定ID来查询相册(album)信息。
func albumByID(id int64) (Album, error) {
    // 声明一个用来存储返回行数据的Album结构体变量。
    var alb Album

    row := db.QueryRow("SELECT * FROM album WHERE id = ?", id)
    if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
        if err == sql.ErrNoRows {
            return alb, fmt.Errorf("albumsById %d: no such album", id)
        }
        return alb, fmt.Errorf("albumsById %d: %v", id, err)
    }
    return alb, nil
}

//addAlbum函数添加指定专辑的数据到数据库,返回这条新记录的ID
func addAlbum(alb Album) (int64, error) {
    result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
    if err != nil {
        return 0, fmt.Errorf("addAlbum: %v", err)
    }
    id, err := result.LastInsertId()
    if err != nil {
        return 0, fmt.Errorf("addAlbum: %v", err)
    }
    return id, nil
}

Navicat出现Access violation at address 00000000. Read of address 00000000问题的解决方法

我的操作系统是Winodws 10,在使用Navicat Premium 16的模型或图表工作区时,弹出一个错误对话框说Access violation at address 00000000. Read of address 00000000。

出现这个问题的原因,应该是Winodws 10操作系统里安装的C++运行库与Navicat Premium 16所要求的版本不一致。Navicat Premium 16在Winodws 7操作系统里并不会出现这个问题。

因此这个问题的解决方法可以是,先在Winodws 10操作系统里安装VirtualBox,再安装Winodws 7虚拟机,然后在该虚拟机里安装和使用Navicat Premium 16。

关系型数据库的列和行的所有别名

列的别名:字段、分量、属性

行的别名:记录、元组、向量

从二维表的角度,二维表由列和行组成。

从关系代数的角度,一张二维表对应一个关系,一行对应一个元组(向量),列对应分量。

从ORM的角度,一张二维表对应一个类,二维表的列对应类的属性,二维表的一行数据对应类的一个实例。

从信息世界的角度,一张二维表对应一个实体型(实体类型),列对应实体的属性,一行对应一个实体。

从机器世界的角度,一张二维表对应一个文件,一行数据对应一条记录,列对应字段。关系型数据库管理系统(RDMS)多使用机器世界的术语。