Golang,postgres事务:pq:在失败的事务中出现意外的事务状态。

huangapple go评论89阅读模式
英文:

Golang, postgres transaction: pq: unexpected transaction status in a failed transaction

问题

Go: v 1.3
数据库: 使用 lib/pq 连接的 postgres

我有一个更新 postgres 数据库的应用程序。该 postgres 数据库是使用 pgbouncer 设置的。

因此,在活动连接下,我有代码来运行插入和更新操作。这是插入代码:

func (sitemap *SiteMapData) InsertSiteMap(dbConnection *sql.DB) (int64, error) {

    tx, err := dbConnection.Begin()
    if err != nil {
        l4g.Error("InsertSiteMap: could not begin transaction: %v", err)
        return 0, err
    }
    result, err := tx.Exec("INSERT INTO sitemap (url) VALUES($1)", sitemap.Url)

    if err != nil {
        if !strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
            l4g.Error("sitemapdata.InsertSiteMap: error inserting new sitemap data: %v", err)
        }
        tx.Rollback()
        return 0, nil
    }

    resultCount, _ := result.RowsAffected()
    if err := tx.Commit(); err != nil {
        l4g.Error("InsertSiteMap: could not commit transaction: %v", err)
        return resultCount, err
    }
    l4g.Info("InsertSiteMap: Insert with %s completed with count: %d", sitemap.Url, resultCount)
    return resultCount, nil
}

当我启动应用程序时,这段代码正常工作,更新代码也是如此:

func (sitemap *SiteMapData) PersistSiteMapData(dbConnection *sql.DB) (int64, error) {

    baseQuery, execType := sitemap.buildUpdateQuery()

    // 由于语句中的参数数量可能会有所不同,因此需要以下逻辑
    var result sql.Result
    var execErr error

    tx, err := dbConnection.Begin()
    if err != nil {
        l4g.Error("PersistSiteMap: could not begin transaction: %v", err)
        return 0, err
    }

    switch {
    case execType == NoExtraDates:
        result, execErr = tx.Exec(baseQuery, sitemap.ConsecutiveFailCount, sitemap.LastAttempt.Time,
            sitemap.Etag.String, sitemap.InternalChecksum.String, sitemap.Id)
    case execType == LastGatheredOnly:
        result, execErr = tx.Exec(baseQuery, sitemap.ConsecutiveFailCount, sitemap.LastAttempt.Time,
            sitemap.Etag.String, sitemap.InternalChecksum.String,
            sitemap.LastGathered.Time, sitemap.Id)
    case execType == ModifiedHeaderOnly:
        result, execErr = tx.Exec(baseQuery, sitemap.ConsecutiveFailCount, sitemap.LastAttempt.Time,
            sitemap.Etag.String, sitemap.InternalChecksum.String,
            sitemap.ModifiedHeader.Time, sitemap.Id)
    case execType == BothDates:
        result, execErr = tx.Exec(baseQuery, sitemap.ConsecutiveFailCount, sitemap.LastAttempt.Time,
            sitemap.Etag.String, sitemap.InternalChecksum.String,
            sitemap.LastGathered.Time, sitemap.ModifiedHeader.Time, sitemap.Id)
    }

    if execErr != nil {
        tx.Rollback()
        return -1, fmt.Errorf("PersistSiteMapData Error %s: %v", baseQuery, execErr)
    }

    resultCount, _ := result.RowsAffected()
    if err := tx.Commit(); err != nil {
        l4g.Error("PersistSiteMap: could not commit transaction: %v", err)
        return resultCount, err
    }
    l4g.Info("PersistSiteMapData Updated sitemap %s(%d) correctly", sitemap.Url, sitemap.Id)
    return resultCount, nil
}

// buildUpdateQuery 根据有效的日期时间字段的存在返回更新查询语句。
func (sitemap *SiteMapData) buildUpdateQuery() (string, int) {
    // 注意: 此处不包括 lastAttempt,因为它是在检索尝试之前立即设置的
    nextParam := 5
    execType := NoExtraDates
    baseQuery := "UPDATE sitemap " +
        "SET " +
        "consecutive_fail_count = $1, last_attempt = $2, etag = $3, internal_checksum = $4"

    if sitemap.LastGathered.Valid {
        baseQuery = fmt.Sprintf("%s, last_gathered = $%d", baseQuery, nextParam)
        nextParam++
        execType += LastGatheredOnly
    }

    if sitemap.ModifiedHeader.Valid {
        baseQuery = fmt.Sprintf("%s, modified_header = $%d", baseQuery, nextParam)
        nextParam++
        execType += ModifiedHeaderOnly
    }
    baseQuery = fmt.Sprintf("%s WHERE id = $%d", baseQuery, nextParam)

    return baseQuery, execType
}

日志显示更新首先是正确的,然后过一段时间后再次检查日志,我看到:

pq: unexpected transaction status in a failed transaction

通过跟踪 lib/pq 代码,看起来当调用 *sql.DB.Begin() 时,已经存在一个事务时会出现这个问题。

我想知道是否有人能够对此提供更多的信息?

我在想,也许我应该专门跟踪该消息,如果遇到它,就进入基于时间的重试循环?或者是否有一种方法可以找到错误的事务并将其终止?

谢谢
Nathan

英文:

Go: v 1.3
db: postgres using lib/pq

I have an app that updates a postgres database. The postgres database is set up using pgbouncer.

So, with an active connection I have code to run inserts and updates. Here is the insert code:

func (sitemap *SiteMapData) InsertSiteMap(dbConnection *sql.DB) (int64, error) {
tx, err := dbConnection.Begin()
if err != nil {
l4g.Error("InsertSiteMap: could not being transaction: %v", err)
return 0, err
}
result, err := tx.Exec("INSERT INTO sitemap (url) VALUES($1)", sitemap.Url)
if err != nil {
if !strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
l4g.Error("sitemapdata.InsertSiteMap: error inserting new sitemap data: %v", err)
}
tx.Rollback()
return 0, nil
}
resultCount, _ := result.RowsAffected()
if err := tx.Commit(); err != nil {
l4g.Error("InsertSiteMap: could not commit transaction: %v", err)
return resultCount, err
}
l4g.Info("InsertSiteMap: Insert with %s completed with count: %d", sitemap.Url, resultCount)
return resultCount, nil
}

When I start up the app this works just fine as does the update code:

func (sitemap *SiteMapData) PersistSiteMapData(dbConnection *sql.DB) (int64, error) {
baseQuery, execType := sitemap.buildUpdateQuery()
// as the number of parameters in the statement may vary the following logic is needed
var result sql.Result
var execErr error
tx, err := dbConnection.Begin()
if err != nil {
l4g.Error("PersistSiteMap: could not being transaction: %v", err)
return 0, err
}
switch {
case execType == NoExtraDates:
result, execErr = tx.Exec(baseQuery, sitemap.ConsecutiveFailCount, sitemap.LastAttempt.Time,
sitemap.Etag.String, sitemap.InternalChecksum.String, sitemap.Id)
case execType == LastGatheredOnly:
result, execErr = tx.Exec(baseQuery, sitemap.ConsecutiveFailCount, sitemap.LastAttempt.Time,
sitemap.Etag.String, sitemap.InternalChecksum.String,
sitemap.LastGathered.Time, sitemap.Id)
case execType == ModifiedHeaderOnly:
result, execErr = tx.Exec(baseQuery, sitemap.ConsecutiveFailCount, sitemap.LastAttempt.Time,
sitemap.Etag.String, sitemap.InternalChecksum.String,
sitemap.ModifiedHeader.Time, sitemap.Id)
case execType == BothDates:
result, execErr = tx.Exec(baseQuery, sitemap.ConsecutiveFailCount, sitemap.LastAttempt.Time,
sitemap.Etag.String, sitemap.InternalChecksum.String,
sitemap.LastGathered.Time, sitemap.ModifiedHeader.Time, sitemap.Id)
}
if execErr != nil {
tx.Rollback()
return -1, fmt.Errorf("PersistSiteMapData Error %s: %v", baseQuery, execErr)
}
resultCount, _ := result.RowsAffected()
if err := tx.Commit(); err != nil {
l4g.Error("PersistSiteMap: could not commit transaction: %v", err)
return resultCount, err
}
l4g.Info("PersistSiteMapData Updated sitemap %s(%d) correctly", sitemap.Url, sitemap.Id)
return resultCount, nil
}
// buildUpdateQuery returns the update query dependent on the presence of valid datetime fields.
func (sitemap *SiteMapData) buildUpdateQuery() (string, int) {
// note: lastAttempt is not covered here as this is set immediatley prior to the retrieval attempt
nextParam := 5
execType := NoExtraDates
baseQuery := "UPDATE sitemap " +
"SET " +
"consecutive_fail_count = $1, last_attempt = $2, etag = $3, internal_checksum = $4"
if sitemap.LastGathered.Valid {
baseQuery = fmt.Sprintf("%s, last_gathered = $%d", baseQuery, nextParam)
nextParam++
execType += LastGatheredOnly
}
if sitemap.ModifiedHeader.Valid {
baseQuery = fmt.Sprintf("%s, modified_header = $%d", baseQuery, nextParam)
nextParam++
execType += ModifiedHeaderOnly
}
baseQuery = fmt.Sprintf("%s WHERE id = $%d", baseQuery, nextParam)
return baseQuery, execType
}

The logs show that the updates are happening correctly at first and then I check the logs again after a while and I see :

pq: unexpected transaction status in a failed transaction

Tracking through the lib/pq code it looks like this comes about when a *sql.DB.Begin() is called and a transaction is already running.

I wonder if anyone can shed any more light on this?

I'm kinda thinking that perhaps I should track that message specifically and if I get it enter a time based retry loop? Or is there a way to find the errant transaction and kill it off?

Thanks
Nathan

答案1

得分: 1

从http://golang.org/pkg/database/sql/#DB.Begin:

> Begin开始一个事务。隔离级别取决于驱动程序。

所以看起来lib/pq在处理事务的隔离级别方面表现不佳。

唯一的解决方法是使用sync.Mutex在本地锁定事务,并在其问题跟踪器上提交错误报告,因为这是一个驱动程序的错误。

英文:

From http://golang.org/pkg/database/sql/#DB.Begin:

> Begin starts a transaction. The isolation level is dependent on the driver.

So it appears lib/pq doesn't handle isolation well for transactions.

The only workaround is to use a sync.Mutex to lock transactions locally and file a bug on their issue tracker since this is a driver bug.

huangapple
  • 本文由 发表于 2014年9月16日 17:03:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/25864670.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定