【发布时间】: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