Sqlite并发写作性能

时间:2016-03-04 19:50:50

标签: go sqlite

我正在用Golang和Sqlite3编写一个网站,我希望每天大约有1000个并发文章,每天几分钟,所以我做了以下测试(忽略错误检查看起来更干净):

t1 := time.Now()
tx, _ := db.Begin()
stmt, _ := tx.Prepare("insert into foo(stuff) values(?)")
defer stmt.Close()

for i := 0; i < 1000; i++ {
    _, _ = stmt.Exec(strconv.Itoa(i) + " - ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,./;'[]-=<>?:()*&^%$#@!~`")
}

tx.Commit()
t2 := time.Now()
log.Println("Writing time: ", t2.Sub(t1))

写作时间约为0.1秒。然后我将循环修改为:

for i := 0; i < 1000; i++ {
    go func(stmt *sql.Stmt, i int) {
        _, err = stmt.Exec(strconv.Itoa(i) + " - ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,./;'[]-=<>?:()*&^%$#@!~`")
        if err != nil {
            log.Fatal(err)
        }
    }(stmt, i)
}

这让我神圣46.2秒!我跑了很多次,每次超过40秒!有时甚至超过一分钟!由于Golang同时处理每个用户,这是否意味着我必须切换数据库才能使网页正常工作?谢谢!

1 个答案:

答案 0 :(得分:21)

我最近在Go中为网络应用程序评估了SQLite3的性能,并了解到它甚至可以在远程使用之前进行一些设置。

打开预写日志记录

您需要使用WAL PRAGMA journal_mode=WAL。这主要是为什么你会遇到如此糟糕的表现。使用WAL,我可以在几秒钟内完成10000次无需事务的写入。在交易中,它将是闪电般的。

禁用连接池

我使用mattn/go-sqlite3并打开一个带有SQLITE_OPEN_FULLMUTEX标志的数据库。这意味着每个SQLite调用都将被锁定。一切都将被序列化。这实际上是你想要的SQLite。 Go在这种情况下的问题是你会得到随机错误,告诉你数据库已被锁定。原因是因为sql/DB在内部工作的方式。在它内部为您管理连接池,因此它将打开多个SQLite连接,您不希望这样做。为了解决这个问题,我必须基本上禁用池。致电db.SetMaxOpenConns(1),它会起作用。即使在非常高的负载下,也会有成千上万的读写操作,但它没有问题。

其他解决方案可能是使用SQLITE_OPEN_NOMUTEX在多线程模式下运行SQLite并让它为您管理。但SQLite并不适用于多线程应用程序。读取可以并行发生,但一次只能写入一次。你偶尔会遇到busy错误,这些错误对于SQLite来说是完全正常的,但是需要你对它们做些什么 - 你可能不希望在发生这种情况时完全停止写操作。这就是为什么大多数时候人们同步使用SQLite或者只是为SQLite发送一个单独的线程。