【问题标题】:MySQL query sometimes deadlocksMySQL 查询有时会死锁
【发布时间】:2020-01-16 15:51:31
【问题描述】:

我正在开发一个对 MySQL 进行查询的程序,然后为每一行更改该行的某些内容,然后更新该行。

问题是,有时在执行更新时我会遇到死锁,我不确定是因为在我更新时查询没有释放锁,还是其他原因。

我在做什么的例子:

const (
    selectQuery = `select user_id, original_transaction_id, max(payment_id) as max_payment_id from Payment_Receipt 
                        where auto_renew_status = 1 group by user_id, original_transaction_id having count(*) > 1`
    updateQuery = `update Payment_Receipt set auto_renew_status = 0, changed_by = "payment_receipt_condenser", 
changed_time = ? where user_id = ? and original_transaction_id = ? and payment_id != ? and auto_renew_status = 1`
)

mysql.go:

func New(db *sql.DB, driver string) (database.Database, error) {
    sqlDB := sqlx.NewDb(db, driver)

    if err := db.Ping(); err != nil {
        return nil, errors.Wrap(err, "connecting to database")
    }

    selectStmt, err := sqlDB.Preparex(selectQuery)
    if err != nil {
        return nil, errors.Wrap(err, "preparing select query")
    }

    updateStmt, err := sqlDB.Preparex(updateQuery)
    if err != nil {
        return nil, errors.Wrap(err, "preparing update query")
    }

    return &mysql{
        db:         sqlDB,
        selectStmt: selectStmt,
        updateStmt: updateStmt,
    }, nil
}

func (m *mysql) Query() (<- chan *database.Row, error) {
    rowsChan := make(chan *database.Row)

    rows, err := m.selectStmt.Queryx()
    if err != nil {
        return nil, errors.Wrap(err, "making query")
    }

    go func() {
        defer rows.Close()
        defer close(rowsChan)

        for rows.Next() {
            row := &database.Row{}

            if err := rows.StructScan(row); err != nil {
                log.WithError(err).WithField("user_id", row.UserID.Int32).Error("scanning row")
            }

            // change some of the data here
            // and put into channel for worker to consume

            rowsChan <- row
        }
    }()

    return rowsChan, nil
}

func (m *mysql) Update(row *database.Row) error {
    tx, err := m.db.Beginx()
    if err != nil {
        return errors.Wrap(err, "beginning transaction")
    }

    if _, err := tx.Stmtx(m.updateStmt).Exec(row.ChangedTime); err != nil {
        return errors.Wrap(err, "executing update")
    }

    if err := tx.Commit(); err != nil {
        return errors.Wrap(err, "committing transaction")
    }

    return nil
}

worker.go

func (w *worker) Run(wg *sync.WaitGroup) {
    rowsChan, err := w.db.Query()
    if err != nil {
        log.WithError(err).Fatal("failed making query")
    }

    for i := 0; i < w.config.Count(); i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for row := range rowsChan {
                if err := w.db.Update(row); err != nil {
                    log.WithError(err).WithField("user_id", row.UserID.Int32).Error("updating row")
                }
            }
        }()
    }
}

【问题讨论】:

  • 我怀疑在w.db.Update() 调用期间,初始查询仍在生成行——这就是死锁的根源。一个简单的“创可贴”修复方法是使(行)通道缓冲。
  • 为了清楚起见,您能否确认您遇到了 MySQL 死锁(而不是 goroutine 死锁)。如果是,请分享 selectQuery 和 updateQuery。
  • @Brits 是的,我可以确认这是 MySQL 死锁。我得到:Deadlock found when trying to get lock; try restarting transaction" user_id=2491427。我已经编辑了帖子以包含查询。
  • 谢谢;我对您的 update 语句有点困惑(它有 4 个参数标记,但您只传递一个值 tx.Stmtx(m.updateStmt).Exec(row.ChangedTime))。假设我错过了参数,下一步是确认死锁的原因(否则我们真的在猜测);运行SHOW ENGINE INNODB STATUS 是最简单的方法(有关说明和其他选项,请参阅the docs)。

标签: mysql go concurrency


【解决方案1】:

您可以从Query() 缓冲结果(行)通道:

func (m *mysql) Query() (<- chan *database.Row, error) {

    rowsChan := make(chan *database.Row, 1000) // <- band-aid fix

    // ...
}

这将确保行收集器函数可以写入多个结果,而无需等待您的工作程序 go-routine 读取结果。查询操作将完成(假设有 1000 行或更少),更新 go-routine 操作可以开始它们的并行工作。

如果这能解决问题,那么考虑对您的查询设置一个 SQL 限制(例如 LIMIT 1000),以确保您不会再次遇到死锁(如果 1000 多条记录确实有可能)。

制作“分页”样式查询以获取接下来的 1000 行,使用 RowID 标记等以确保完全覆盖结果 - 同时避免锁定您的任何 update 操作。

【讨论】:

  • 所以我尝试了,我将通道的大小设置为w.config.Count(),设置为 10,因此将有 10 个工作人员,通道大小也将是 10,我遇到了同样的错误.还是您的意思是创建一个缓冲通道但只返回一次rows.Next() 返回false
  • 刚刚编辑的答案。我怀疑死锁是当第一个工作人员从结果通道中读取第一行并尝试执行db.Update() 时——同时 Query() 函数正在尝试写入下一行结果。向结果通道添加缓冲区可以完成初始查询 - 从而消除共享资源死锁的可能性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多