【问题标题】:Inserting Rows in MySQL in Go very slow?在 Go 中的 MySQL 中插入行非常慢?
【发布时间】:2013-10-24 07:02:30
【问题描述】:

所以我一直在重写一个旧的 PHP 系统来寻找一些性能提升,但我没有得到任何提升。问题似乎出在我正在对 Mysql 进行的插入操作中。

因此,PHP 对 CSV 文件进行一些处理、进行一些散列处理并在 MySQL 中插入大约 10k 行需要 40 秒(未优化的代码)。

另一方面,现在 Go 去掉任何处理,同样插入 10k(空)行需要 110 秒。

两个测试都在同一台机器上运行,我使用的是 go-mysql-driver。

现在是一些 Go 代码:

这是非常简单的代码,与 PHP 相比,这仍然需要将近 2 分钟。

db := GetDbCon()
defer db.Close()

stmt, _ := db.Prepare("INSERT INTO ticket ( event_id, entry_id, column_headers, column_data, hash, salt ) VALUES ( ?, ?, ?, ?, ?, ? )")

for i := 0; i < 10000; i++{
    //CreateTicket(columns, line, storedEvent)
    StoreTicket(models.Ticket{int64(0), storedEvent.Id, int64(i),
                    "", "", "", "", int64(0), int64(0)}, *stmt)
}

//Extra functions
func StoreTicket(ticket models.Ticket, stmt sql.Stmt){
    stmt.Exec(ticket.EventId, ticket.EntryId, ticket.ColumnHeaders, ticket.ColumnData, ticket.Hash, ticket.Salt)
}

func GetDbCon() (sql.DB) {
    db, _ := sql.Open("mysql", "bla:bla@/bla")

    return *db
}

Profiler result

那是我的代码,go-mysql-driver 还是这是正常的,PHP 在插入记录时真的很快吗?

==编辑==

根据要求,我用 tcpdump 记录了 PHP 和 Go 的运行情况: 文件:

比较这两个日志我很难得出任何结论,两个日志似乎都在来回发送相同大小的数据包。但是使用 Go(~110) mysql 处理请求的时间几乎是 PHP(~44) 的两倍,Go 似乎在再次发送新请求之前等待稍长一些(尽管差异很小)。

【问题讨论】:

  • 同机没问题,但是是同一个数据库和同一个表吗?您是否定义了任何额外的索引?只是一个疯狂的猜测。
  • 它们在不同的数据库上运行,命名略有不同(结构相同),但索引完全相同,并且每次测试前都会截断数据库。
  • 您使用的是什么 Go 版本和 MySQL 驱动程序?
  • go1.1.2 linux/amd 和来自 github 的最新 go-mysql-driver 文件
  • 最后,虽然这不是您问题的答案,但您可能想尝试 Mysql 的多行插入语句 (insert into ... values (row1), (row2)...),并通过基准找到每个语句的最佳行数。如果插入顺序对您的应用程序无关紧要,您还可以使用 goroutine 并行发出多个语句。

标签: mysql go


【解决方案1】:

这是一个老问题,但仍然 - 迟到总比没有好;你要请客了:

将所有数据放入bytes.Buffer 中,以制表符分隔、换行符终止和未加引号的行(如果文本引起问题,必须先转义)。 NULL 必须编码为\N

使用http://godoc.org/github.com/go-sql-driver/mysql#RegisterReaderHandler 并在“instream”下注册一个返回该缓冲区的函数。接下来,调用LOAD DATA LOCAL INFILE "Reader::instream" INTO TABLE ... - 这是一种将数据泵入 MySQL 的非常快速的方法(我从标准输入管道传输的文件中测量了大约 19 MB/秒,而从标准输入上传数据时 MySQL 命令行客户端为 18 MB/秒)。

据我所知,该驱动程序是在不需要文件的情况下加载 DATA LOCAL INFILE 的唯一方法。

【讨论】:

    【解决方案2】:

    我注意到您没有使用事务,如果您使用带有 InnoDB 的 vanilla mysql 5.x,这将是一个巨大的性能拖累,因为它会在每次插入时自动提交。

    func GetDbCon() (sql.DB) {
        db, _ := sql.Open("mysql", "bla:bla@/bla")
        return *db
    }
    
    func PrepareTx(db *db.DB,qry string) (tx *db.Tx, s *db.Stmt, e error) {
     if tx,e=db.Begin(); e!=nil {
      return
     }
    
     if s, e = tx.Prepare(qry);e!=nil {
      tx.Close()
     }
     return
    }
    
    
    db := GetDbCon()
    defer db.Close()
    
    qry := "INSERT INTO ticket ( event_id, entry_id, column_headers, column_data, hash, salt ) VALUES ( ?, ?, ?, ?, ?, ? )"
    
    tx,stmt,e:=PrepareTx(db,qry)
    if e!=nil {
     panic(e)
    }
    
    defer tx.Rollback()
    for i := 0; i < 10000; i++{
     ticket:=models.Ticket{int64(0), storedEvent.Id, int64(i),"", "", "", "", int64(0), int64(0)}
     stmt.Exec(ticket.EventId, ticket.EntryId, ticket.ColumnHeaders, ticket.ColumnData, ticket.Hash, ticket.Salt)
    
     // To avoid huge transactions
     if i % 1000 == 0 {
      if e:=tx.Commit();e!=nil {
       panic(e)
      } else {
       // can only commit once per transaction
       tx,stmt,e=PrepareTx(db,qry)
       if e!=nil {
        panic(e)
       }
      }
     }
    }
    
    // Handle left overs - should also check it isn't already committed
    if e:=tx.Commit();e!=nil {
     panic(e)
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-11-17
      • 2014-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-01
      • 2017-11-01
      相关资源
      最近更新 更多