⽤Go向MySQL导⼊.csv⽂件(转载)
今天来更新⼀个很少碰到,但碰到了⼜让⼈⼗分蛋疼的问题——Go语⾔中执⾏MySQL的load data local infile语句报local file 'xxx' is not registered错误该如何解决。
上车请刷卡,没卡的乘客请投币,上车的乘客请往车厢中部⾛,汽车起步,请坐稳扶好。
情景在现:我要在Go语⾔中执⾏sql语句,往MySQL中导⼊⼀个.csv⽂件,sql语句如下:
load data LOCAL infile './user.csv' into table users
CHARACTER SET utf8 fields terminated by ',' lines terminated by '\r\n' ignore 1 lines(age,name);
数据库驱动⽤的是"github/go-sql-driver/mysql",go语⾔代码如下:
package main
import (
"database/sql"
"fmt"
_ "github/go-sql-driver/mysql"
)
var (
db *sql.DB
dburl = "root:******@/test?charset=utf8&parseTime=True&loc=Local"
sql = `load data LOCAL infile 'F://user.csv' into table users CHARACTER SET utf8 fields terminated by ',' lines terminated by '\r\n' ignore 1 lines(age,name);` )
func init() {
var err error
db, err = sql.Open("mysql", dburl)
if err != nil {
panic(err)
}
}
func main() {
_, err := db.Exec(sql)
if err != nil {
fmt.Println(err)
}
}
[注:以上代码不能直接⾷⽤,除⾮你装了MySQL]
⼀切看起来都很美好,然⽽运⾏的时候。。。会报错下⾯的错误。
local file 'F://user.csv' is not registered
什么⿁?⽂件未注册?⽂件还需要注册?明明在cmd⾥⾯执⾏这句sql就没有问题,偏偏在go⾥⾯就不⾏,⽽且怎么弄都不⾏,简直想砸电。。“电脑太贵了”。。简直想砸键盘,然⽽。。。。砸完键盘之后,问题还是要解决的。
经过⼀番挖地三尺的搜索,终于到了问题的根源所在。
在MySQL驱动的⽂件中,有⼀个叫handleInFileRequest的函数,其中有这样⼀段代码:
func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
...
fr := fileRegister[name] // 这⾥的 name 就是⽂件名:F://user.csv
if mc.cfg.AllowAllFiles || fr {
var file *os.File
var fi os.FileInfo
if file, err = os.Open(name); err == nil {
defer deferredClose(&err, file)
// get file size
if fi, err = file.Stat(); err == nil {
rdr = file
if fileSize := int(fi.Size()); fileSize < packetSize {
packetSize = fileSize
}
}
}
} else {
err = fmt.Errorf("local file '%s' is not registered", name) //<--根源所在
}
...
}
错误就来⾃这段代码,问题很明显了,mc.cfg.AllowAllFiles = false并且fr为空。因此我们可以从两个⽅⾯来解决这个问题。
⽅案⼀、解决fr为空的问题。fileRegister实际上是⼀个map,在中定义,并且有⼀个函数作为接⼝:
package mysql
var (
fileRegister map[string]bool
)
func RegisterLocalFile(filePath string) {
fileRegisterLock.Lock()
// lazy map init
if fileRegister == nil {
fileRegister = make(map[string]bool)
}
fileRegister[strings.Trim(filePath, `"`)] = true
fileRegisterLock.Unlock()
}
RegisterLocalFile是⼀个导出函数,可以在我们的main函数中调⽤,但前提是不能以_的⽅式导⼊驱动包github/go-sql-driver/mysql。将代码修改如下:
import (
"database/sql"
"fmt"
"github/go-sql-driver/mysql"
)
var (
db *sql.DB
dburl = "root:******@/test?charset=utf8&parseTime=True&loc=Local"
sql = `load data LOCAL infile 'F://user.csv' into table users CHARACTER SET utf8 fields terminated by ',' lines terminated by '\r\n' ignore 1 lines(age,name);` )
func init() {
var err error
db, err = sql.Open("mysql", dburl)
if err != nil {
panic(err)
}
}
func main() {
mysql.RegisterLocalFile("F://user.csv")
_, err := db.Exec(sql)
if err != nil {
fmt.Println(err)
}
}
运⾏成功。需要注意的是,sql语句中的⽂件名和RegisterLocalFile带⼊的⽂件名必须完全⼀样,⽽且是绝对路径。
⽅案⼆、我们还可以通过修改mc.cfg.AllowAllFiles的值来解决这个问题。handleInFileRequest是mysqlConn的函数,我们需要看
看mysqlConn的定义。
type mysqlConn struct {
...
cfg *Config
...
}
去掉那些⽆关紧要的字段,我们看到cfg字段是*Config类型,再看看Config结构体的定义。
type Config struct {
...
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
...
}
看到这⾥是不是很开⼼呢?不过不要开⼼的太早了,虽然AllowAllFiles是导出的,但是cfg是未导出字段,并且mysqlConn并没有提供有
关cfg的接⼝,玩个蛋。。。
就在我以为此路不通⽽⼼灰意冷⾃怨⾃艾濒临放弃怨天尤⼈的时候,天⽆绝⼈之路。
在中,我发现了这样⼀个函数:
func parseDSNParams(cfg *Config, params string) (err error) {
for _, v := range strings.Split(params, "&") {
param := strings.SplitN(v, "=", 2)
if len(param) != 2 {
continue
}
// cfg params
switch value := param[1]; param[0] {
// Disable INFILE whitelist / enable all files
case "allowAllFiles":
var isBool bool
cfg.AllowAllFiles, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
...
}
}
}
还记得sql.Open函数吗?他的第⼆个参数需要⼀个字符串来指定⽤户名,密码,表名等等信息。parseDSNParams函数就是在解析这个字符串,并且设置相应的参数。所以我们只需要在原来的dburl最后加上&allowAllFiles=true就可以了。
dburl = "root:******@/test?charset=utf8&parseTime=True&loc=Local&allowAllFiles=true"
其他都不⽤改,再次运⾏,成功。
parse error怎么解决本⽅案只针对MySQL使⽤github/go-sql-driver/mysql作为数据库驱动的情况,⾄于⽤的是database/sql还是gorm还是sqlx都没关系。如果数据库驱动不是go-sql-driver/mysql,遇到类似的问题也可参考以上的思路。
终点站到了,请您携带好随⾝物品,下车请⾛好,欢迎⼤家再次乘坐。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论