【问题标题】:Updating using `database/sql` in Go is slow在 Go 中使用 `database/sql` 更新很慢
【发布时间】:2021-11-07 12:37:18
【问题描述】:

我正在对本地 MySQL 实例运行更新查询,更新单行似乎需要大约 8 毫秒。那看起来真的很慢。我可以做些什么来提高性能?

db, _ := sql.Open("mysql", "user:pass@(localhost:3306)/dbname")
db.Exec(`CREATE TABLE topics(
            topic_id                            INT AUTO_INCREMENT PRIMARY KEY,
            title                               VARCHAR(250) NOT NULL,
            content                             TEXT,
            is_sticky                           BOOL NOT NULL DEFAULT false,
            is_readonly                         BOOL NOT NULL DEFAULT false,
            num_comments                        INT NOT NULL DEFAULT 0,
            num_views                           INT NOT NULL DEFAULT 0,
            activity_at                         DATETIME DEFAULT CURRENT_TIMESTAMP,
            archived_at                         DATETIME DEFAULT CURRENT_TIMESTAMP,
            created_at                          DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at                          DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);`)
db.Exec("INSERT INTO topics(title) VALUES(?);", "Name")

since := time.Now()
for i := 0; i < 100; i++ {
    db.Exec("UPDATE topics SET num_views = ? WHERE topic_id = 1;", i)
}
fmt.Println(time.Since(since))

在 Core i7-4790K 上完成 100 次迭代大约需要 850 毫秒。我也用 Postgres 尝试了同样的事情,结果或多或少相同。我创建了这个测试代码,因为我的 web 应用程序非常慢,每个请求大约 10 毫秒(250 个请求/秒的吞吐量),而且结果证明更新需要很长时间。

此外,使用准备好的语句没有任何区别。

stmt, _ := db.Prepare("UPDATE topics SET num_views = ? WHERE topic_id = ?;")
since := time.Now()
for i := 0; i < 100; i++ {
    stmt.Exec(i, 1)
}
fmt.Println(time.Since(since))

【问题讨论】:

  • 您可以使用单个查询而不是一百个(至少在 PostgreSQL 中,不确定 MySQL 是否也对此提供本机支持,但我认为确实如此)。
  • @mkopriva 是的,我希望单个查询或将它们全部放入事务中会有所帮助,但我的问题是单个更新速度很慢,这导致我的 webapp 的 HTTP 处理程序性能不佳。
  • 您的数据库设置出了点问题,或者其他问题,看起来不是 Go 问题:imgur.com/LjHdDtn(postgres,在我的机器上大约 6 毫秒)
  • 检查是否有锁阻止更新,但您应该寻找其他方法来保存视图。
  • “我的 web 应用程序非常慢,每个请求大约 10 毫秒” -- @spiky 你没有按照我希望的每个请求执行 sql.Open,还是你?

标签: mysql go


【解决方案1】:

(从 MySQL 的角度讲...)

一些“经验法则”:

  • 单个INSERT:10ms
  • 单个INSERT 插入100 行或更多行:每行快10 倍。
  • BEGIN; INSERT...; INSERT...; ... COMMIT;:也是 10 倍。
  • 以上假设HDD; SSD 可能会再快 10 倍。
  • 如果多个连接都在执行插入操作,它们可能能够并行运行。 10 个线程可能能够在相同的经过时间内完成 5 倍的工作。 (当然,这可能会给应用增加不必要的复杂性。)

UPDATE 的数字相似,但使用单个查询对不同行进行不同更新并不容易。

您的测试显示每次执行一行时每行 8.5 毫秒 UPDATEd。使用BEGIN...COMMIT 对所有 100 行进行批处理可能需要大约 85 毫秒,即使在 HDD 上也是如此。

一些应用程序适合批处理;有些没有。如果您想谈论提高 MySQL 性能,我们需要深入了解您的应用程序的细节。

“点赞”和“查看”计数器可能需要移动到“平行”表中,因为它们往往会一次更新,并会干扰其他活动。它们还倾向于自动允许多线程,因此每 100 次的时间远少于 850 毫秒。在非常高的活动中(例如每秒超过 1K 次查看),可以通过额外的应用程序代码人为地对此类计数器进行批处理。

请重写您的基准以反映实际应用程序中将发生的活动。 (我猜测更新将并行发生,而不是串行发生。它们将随时间随机分布。)

另一件事...如果每个“查看次数”都来自一个 Web 服务器,那么还有连接和断开连接;因此 经过 时间可能超过 8.5 毫秒。但“过去”不是关键问题;真正的问题是“每秒可以执行多少更新”。)

还有一件事...如果您测试“并行”,请不要在每个请求中访问同一行。这可能会比你打不同的行要慢得多。 (打随机行会更好。对打哪一行有偏见会更现实。)

【讨论】:

  • 我在 Postgres 中切换到异步提交,100 次顺序更新的时间从 850ms 下降到 8ms。 synchronous_commit=OFF 在 MySQL 中的等价物是什么?
  • 更新语句位于一个端点中,该端点被调用以更新主题的视图数。因此,它会在每次页面加载时被调用。
  • 在 MySQL 中,将innodb_flush_log_at_trx_commit 设置为 0 或 2 会将 100 次更新的更新时间缩短到 12 毫秒。
  • @spiky - 我怀疑 MySQL 是否有 synchronous_commit 的精确模拟。请参阅sync_binlog、“半同步复制”、autocommit,以及您提到的那个。我不相信有一个标志可以使 100 次更新运行速度提高 100 倍,即使存在丢失数据的风险。
  • @spiky - 您是否使用主副本复制?还是您只关心单个服务器?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-08
  • 1970-01-01
  • 2016-06-15
  • 2021-03-01
  • 1970-01-01
  • 2018-05-06
  • 2014-12-08
相关资源
最近更新 更多