在Go中使用事务

在Go中使用事务,go,Go,当用户注册时,我的应用程序会做两件事——将用户添加到数据库并发送验证电子邮件: ... err := collection("users").Insert(&u); if err != nil { WriteServerError(w, err) return } if err = sendVerificationEmail(&u); err != nil { WriteServerError(w, err) } ... 如果电子邮件未被发送,我不想将

当用户注册时,我的应用程序会做两件事——将用户添加到数据库并发送验证电子邮件:

...
err := collection("users").Insert(&u);
if err != nil {
    WriteServerError(w, err)
    return
}

if err = sendVerificationEmail(&u); err != nil {
    WriteServerError(w, err)
}
...
如果电子邮件未被发送,我不想将用户添加到数据库中;如果用户未被添加到数据库中,我也不想发送电子邮件(后者当然会按此顺序处理代码块)

假设Go支持事务,那么像这样的东西值得费心吗?如果是这样的话,那么有人能给我一些建议,告诉我如何转换上面的代码吗

我可以使用嵌套语句,但这可能会变得非常难看。

Go中没有“事务”。您最好按照正确的顺序执行以下步骤:

  • 在数据库中插入用户
  • 如果插入失败,则返回
  • 发送电子邮件
  • 如果电子邮件失败,请删除该用户

  • 也就是说,一般来说,没有必要转到步骤4。解决此问题的常用方法是在注册/登录页面的某个位置设置一个“我没有收到验证电子邮件,请再发送一封。”按钮。即使发送正确,您甚至无法确定您的电子邮件是否已送达:它可能会被反垃圾邮件过滤,被误点击删除,等等。

    我认为首先您必须问问自己,为什么会出现数据库/电子邮件发送错误。如果您的数据库不可靠,并且有2%的时间插入失败,那么您可能必须选择其他数据库。如果问题是数据库出现故障,那么您必须担心的问题远不止几个未发送的电子邮件

    如果数据库是可靠的,并且问题在于发送电子邮件,因为您使用了一些第三方提供商,而且有时API会失败,那么您可以使用以下方法

    for {
       if err = sendVerificationEmail(&u); err != nil {
          WriteServerError(w, err)
       } else {
          break
       }
    }
    

    另一种方法是将应保存在数据库中的电子邮件信息保存,并使用另一个进程(每X秒运行一次)检查并发送所有未发送的电子邮件。发送电子邮件后,从表中删除条目。

    最好将用户添加到未验证状态的数据库中。如果他们从未验证过,你可以在几周后将其清除

    您可以为此使用数据库事务

  • 开始交易
  • 插入新用户
  • 尝试发送电子邮件
  • 如果确定,则提交事务
  • 否则回滚
  • 示例代码:

    package main
    
    import (
        "database/sql"
        _ "github.com/lib/pq"
        "log"
        "net/smtp"
    )
    
    func main() {
        db, err := sql.Open("postgres", "user=user dbname=postgres")
        if err != nil {
            log.Fatal(err)
            return
        }
        defer db.Close()
    
        tnx, err := db.Begin()
        if err != nil {
            log.Fatal(err)
            return
        }
    
        user := "test"
        password := "testpassword"
        res, err := tnx.Exec("INSERT INTO users(login, password) VALUES (?, ?)", user, value)
        if err != nil {
            log.Fatal(err)
            err := tnx.Rollback()
            if err != log.Fatal(err) {
                log.Fatal(err)
            }
            return
        }
    
        err := smtp.SendMail(
            "smpt.google.com", smtp.Auth{}, "test@from.com",
            []string{"test@to.com"}, []byte("Hello"))
        if err != nil {
            log.Fatal(err)
            err := tnx.Rollback()
            if err != log.Fatal(err) {
                log.Fatal(err)
            }
            return
        }
        err := tnx.Commit()
        if err != nil {
            log.Fatal(err)
            return
        }
    }
    

    是的,您可以在提交事务时捕获死锁或数据库连接丢失,但这种情况非常罕见,您不必担心。或者,如果您希望获得100%的成功保证,请使用临时用户创建方案。

    在这种特殊情况下,将用户存储在数据库中,稍后在另一个进程中发送电子邮件不是更好吗?您可以在用户上设置一个“状态”列,指示消息是否已成功发送。即使电子邮件尚未发送,您似乎也希望存储该用户。