Server sent charset (255) unknown to the client. Please, report to the developers的解决方法

MySQL 8将默认字符集更改为utf8mb4。但是有些客户端不知道这个字符集。因此,当服务器向客户端报告其默认字符集,而客户端不知道其含义时,就会抛出此错误。

该错误针对C++实现的MySQL Connector,因此它影响的不仅仅是PHP。

正确的解决方案是升级你的客户端,但与此同时我通过将服务器的字符集更改为utf8来使其正常工作,以与未升级的客户端兼容。我将以下配置添加到/etc/my.cnf并重新启动mysqld:

[mysqld]
collation-server = utf8_unicode_ci
character-set-server = utf8

[client]
default-character-set=utf8

[mysql]
default-character-set=utf8

参考

https://stackoverflow.com/questions/43437490/pdo-construct-server-sent-charset-255-unknown-to-the-client-please-rep

https://bugs.mysql.com/bug.php?id=71606

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支持。

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

JDBC连接MySQL数据库警告:Establishing SSL connection without server’s identity verification is not recommend

JDBC连接MySQL出现如下警告:

Establishing SSL connection without server’s identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn’t set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to ‘false’. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.

原因是高版本MySQL需要指明是否进行SSL连接。

解决方法是在JDBC连接MySQL的URL字符串中加入useSSL=true或者useSSL=false即可,例如:

jdbc:mysql://127.0.0.1:3306/framework?serverTimezone=UTC&characterEncoding=utf8&useSSL=false

如果你的连接确实没有暴露给网络(仅限本地主机),或者你在没有真实数据的非生产环境中工作,那么可以肯定的是:通过包含选项useSSL=false来禁用SSL没有坏处。

如果要使用useSSL=true,需要以下一组选项才能使SSL使用证书、主机验证并禁用弱协议选项:

  • useSSL=true
  • sslMode=VERIFY_IDENTITY
  • trustCertificateKeyStoreUrl=file:path_to_keystore
  • trustCertificateKeyStorePassword=password
  • enabledTLSProtocols=TLSv1.2

因此,作为一个示例,让我们假设你希望使用SSL将其中一种Atlassian产品(Jira、Confluence、Bamboo等)连接到MySQL服务器,你需要执行以下主要步骤:

  • 首先,确保你有一个为MySQL服务器主机生成的有效的SSL/TLS证书,并且CA证书安装在客户端主机上(如果你使用自签名,那么你可能需要手动执行此操作,但对于流行的公共CA,它已经在那里了)。
  • 接下来,确保java密钥库包含所有CA证书。在Debian/Ubuntu上,这是通过运行以下命令来实现的:
update-ca-certificates -f
chmod 644 /etc/ssl/certs/java/cacerts
  • 最后,更新Atlassian产品使用的连接字符串以包含所有必需的选项,这在Debian/Ubuntu上类似于:
jdbc:mysql://mysql_server/confluence?useSSL=true&sslMode=VERIFY_IDENTITY&trustCertificateKeyStoreUrl=file%3A%2Fetc%2Fssl%2Fcerts%2Fjava%2Fcacerts&trustCertificateKeyStorePassword=changeit&enabledTLSProtocols=TLSv1.2&useUnicode=true&characterEncoding=utf8

更多相关信息请参见MySQL官方文档https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-using-ssl.html

参考

https://beansandanicechianti.blogspot.com/2019/11/mysql-ssl-configuration.html

CentOS7 yum安装MySql报错The GPG keys listed for the MySQL 8.0 Community Server repository are already installed but they are not correct for this package.的解决方法

在CentOS7中使用yum安装MySql8的时候,遇到这个问题。

原因是,MySQL GPG密钥已过期,无法从官方存储库安装或升级MySQL包。其他详细信息也可以在MySQL网站上找到:https://bugs.mysql.com/bug.php?id=106188

解决方法是,在运行安装程序之前导入密钥:

rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022

对于Ubuntu系统:

wget -q -O - https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 | apt-key add -

参考

https://support.cpanel.net/hc/en-us/articles/4419382481815

https://forums.cpanel.net/threads/mysql-upgrade-process-failed-the-gpg-keys-listed-for-the-mysql-8-0-community-server-repository-are-already-installed-but-they-are-not-correct-for.697213/