在 Heroku 部署期间使用 Go 执行 MySQL 脚本时出现 SQL 语法错误

SQL syntax error when executing MySQL script using Go during Heroku deployment

我正在将使用 Go 制作的 API 和 MySQl 的数据库部署到 Heroku。我在上面关注 guide 并设置了所有内容,但现在我正在尝试执行 MySQL 脚本以使用一些虚拟数据设置表格。但是我不断收到错误消息,说脚本是错误的,即使我在本地使用它没有任何问题。我已经将 MySQL 数据库连接到 Heroku 环境并且启动数据库不会引发任何错误。

以下是将其部署到 Heroku 时显示错误的日志:

2021-06-05T12:49:16.442359+00:00 app[web.1]: ERROR  2021/06/05 12:49:16 main.go:45: Error executing SQL script : Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'USE assessment;

schema.sql

CREATE DATABASE assessment;

USE assessment;

CREATE TABLE users (
    id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, 
    email VARCHAR(255) NOT NULL
);
INSERT INTO `users` VALUES (1,"test1@gmail.com");
INSERT INTO `users` VALUES (2,"test2@gmail.com");
INSERT INTO `users` VALUES (3,"test3@gmail.com");
INSERT INTO `users` VALUES (4,"test4@gmail.com");
INSERT INTO `users` VALUES (5,"test5@gmail.com");

CREATE TABLE features (
    feature_id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
    user_id INTEGER NOT NULL,
    feature_name VARCHAR(100) NOT NULL,
    can_access BOOLEAN NOT NULL DEFAULT FALSE,
    FOREIGN KEY (user_id) REFERENCES users(id) 
);

INSERT INTO `features` VALUES (1, 1, "automated-investing", 1);
INSERT INTO `features` VALUES (2, 1, "crypto", 0);
INSERT INTO `features` VALUES (3, 2, "crypto", 0);
INSERT INTO `features` VALUES (4, 3, "automated-investing", 0);
INSERT INTO `features` VALUES (5, 4, "automated-investing", 1);
INSERT INTO `features` VALUES (7, 1, "financial-tracking", 1);
INSERT INTO `features` VALUES (8, 2, "financial-tracking", 0);
INSERT INTO `features` VALUES (9, 3, "financial-tracking", 1);
INSERT INTO `features` VALUES (10, 4, "financial-tracking", 0);

main.go

package main

import (
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "time"

    _ "github.com/go-sql-driver/mysql"
    "github.com/gorilla/mux"
    "github.com/joho/godotenv"

    "github.com/yudhiesh/api/controller"
    "github.com/yudhiesh/api/middleware"
)

func main() {
    router := mux.NewRouter()
    router.Use(middleware.ResponseHeaders)
    router.Use(middleware.LogRequest)
    errorLog := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)
    infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
    err := godotenv.Load(".env")
    port := os.Getenv("PORT")
    if port == "" {
        errorLog.Fatal("$PORT is not set")
    }
    dsn := os.Getenv("DSN")
    if dsn == "" {
        errorLog.Fatal("$DATABASE_URL is not set")
    }
    db, err := controller.OpenDB(dsn)
    if err != nil {
        errorLog.Fatal(err)
    }
    defer db.Close()
    c, ioErr := ioutil.ReadFile("./schema.sql")
    sqlScript := string(c)
    if ioErr != nil {
        errorLog.Fatalf("Error loading SQL schema : %s", ioErr)
    }
    _, err = db.Exec(sqlScript)
    if err != nil {
        // FAILS HERE!
        errorLog.Fatalf("Error executing SQL script : %s", err)
    }
    app := &controller.Application{
        DB:       db,
        ErrorLog: errorLog,
        InfoLog:  infoLog,
    }
    addr := ":" + port
    server := &http.Server{
        Handler:      router,
        Addr:         addr,
        WriteTimeout: 15 * time.Second,
        ReadTimeout:  15 * time.Second,
    }
    router.HandleFunc("/feature", app.GetCanAccess).Methods("GET")
    router.HandleFunc("/feature", app.InsertFeature).Methods("POST")
    http.Handle("/", router)
    infoLog.Printf("Connected to port %s", port)
    errorLog.Fatal(server.ListenAndServe())
}

好的,所以我决定使用 Heroku 提供的凭据直接连接到 mysql 数据库。

这returns URL 到数据库。

heroku config | grep CLEARDB_DATABASE_URL

看起来像这样:

mysql://alphanum-username:alphanum-password@us-cdbr-iron-east-01.cleardb.net/heroku_alphanum_name?reconnect=true

然后使用 mysql -u username -h host -p 连接到它,主机来自 URL,在本例中是 us-cdbr-iron-east-01.cleardb.net。密码来自 alphanum-password.

我 运行 同样的查询但是得到了这个错误,这意味着我没有访问数据库的权限。

ERROR 1044 (42000): Access denied for user 'bbf806af82a4c9'@'%' to database 'assessment'

运行 show databases; 表明您可以作为用户连接到特定的数据库。

mysql> SHOW DATABASES;
+------------------------+
| Database               |
+------------------------+
| information_schema     |
| herokusdfhsdfhsdfsfsdf |
+------------------------+

从这里我 运行 USE herokusdfhsdfhsdfsfsdf 和 运行 我的其余查询没有问题。

针对 MySQL 的 运行 查询的大多数接口不支持多查询。换句话说,它们不允许用分号分隔多个 SQL 语句,它们每次调用只支持一个 SQL 语句。

在您的情况下,它在 USE ... 上返回语法错误,因为假设输入被解析为单个语句,CREATE DATABASE statement.[=20 中没有 USE =]

CREATE DATABASE assessment; USE assessment;

这意味着如果你想处理一个包含许多 SQL 语句的文件,你不能只做你正在做的事情并将整个文件视为一个字符串来传递给 Exec():

c, ioErr := ioutil.ReadFile("./schema.sql")
sqlScript := string(c)
...
_, err = db.Exec(sqlScript)

您必须将该文件的内容拆分为单独的 SQL 语句,然后遍历这些语句,运行一次一个。

这比听起来更复杂,因为:

  • SQL 脚本可能包含一个 DELIMITER 语句,该语句将语句之间的字符从分号更改为其他字符。

  • 字符串文字或注释中可能有分号。

  • 某些语句,例如 CREATE PROCEDURE、CREATE TRIGGER 等,在例程主体中的语句之间可能包含分号。你不希望这些分号成为语句的结尾,你希望包含所有内容到例程定义的结尾。

  • DELIMITER 语句本身不能 被MySQL 服务器执行。它只控制客户端。因此,您必须将其视为未发送到循环中服务器的异常。事实上,如果您在 SQL 脚本中找到其他一些 mysql client builtin commands,则必须对其进行类似处理。

如果您最终编写了所有这些逻辑,那么您基本上已经在 Go 中重新实现了 MySQL 命令行客户端。

如果您跳过所有编码工作,只使用 os.exec.Command 运行 MySQL 命令行客户端,将会更快更简单。想一想 — 它将为您节省数周的编码工作,复制已在 MySQL 客户端中实现的 运行ning SQL 脚本的所有细微差别。