go-sql-driver 包中包含 init 方法,只要导入包就会被执行,但是按照其实现,该包只能够被导入一次,但是因为 vendor 的作用,会出现该包出现被导入多次的情况,从而在使用次包的时候,最好不要添加 vendor

问题复现

代码

目录结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
├── hello
│   ├── hello.go
│   └── vendor
│       └── github.com
│           └── go-sql-driver
│               └── mysql
│                   ├── appengine.go
│                   ├── AUTHORS
│                   ├── buffer.go
│                   ├── CHANGELOG.md
│                   ├── collations.go
│                   ├── connection.go
│                   ├── const.go
│                   ├── CONTRIBUTING.md
│                   ├── driver.go
│                   ├── dsn.go
│                   ├── errors.go
│                   ├── infile.go
│                   ├── ISSUE_TEMPLATE.md
│                   ├── LICENSE
│                   ├── packets.go
│                   ├── PULL_REQUEST_TEMPLATE.md
│                   ├── README.md
│                   ├── result.go
│                   ├── rows.go
│                   ├── statement.go
│                   ├── transaction.go
│                   └── utils.go
└── main.go

hello.go

1
2
3
4
5
package hello

import (
	_ "github.com/go-sql-driver/mysql"
)

main.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import (
	"fmt"

	_ "haormj.com/test/hello"
	_ "github.com/go-sql-driver/mysql"
)

func main() {
	fmt.Println("hello")
}

现象分析

流程
  1. main 包导入 hello 包
  2. hello 包执行导入 go-sql-driver 包,使用的包为 vendor 的包
  3. 执行 verdor go-sql-driver 包中的 init 方法
  4. main 执行导入 go-sql-driver 包,使用包为 GOPATH 中的包
  5. 执行 GOPATH go-sql-driver 包中的 init 方法
go-sql-driver init 源码
1
2
3
func init() {
sql.Register("mysql", &MySQLDriver{})
}

其中 sql 包为 golang 官方包 "database/sql"

结论

sql.Register("mysql", &MySQLDriver{}) 被执行两次导致 panic

解决方案

github.com/go-sql-driver/mysql 这个包最好不要加到 vendor 中.这样就不会导致`init`方法被执行两次.