事情是这样的。
我们的项目使用的是sqlite3
数据库。在一次常规模拟测试中,我们想了解,如果程序正在运行中,用户将sqlite3
数据库文件删除,程序能否进入预设的重置流程。
结果,我们却让我们有些意外。
try
于是我写了一段go代码模拟一下该过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| package main
import ( "database/sql" "net/http" "time"
_ "github.com/mattn/go-sqlite3" )
func main() { db, err := sql.Open("sqlite3", "file:test.sqlite") if err != nil { panic(err) } defer db.Close()
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS test_t( a TEXT, create_at TIMESTAMP default CURRENT_TIMESTAMP );`)
if err != nil { panic(err) }
http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) { if r.FormValue("query") != "" { rows, err := db.Query("SELECT * FROM test_t") if err != nil { w.Write([]byte(err.Error())) return }
for rows.Next() { var a string var t time.Time err := rows.Scan(&a, &t) if err != nil { w.Write([]byte(err.Error())) return }
w.Write([]byte(a + " | " + t.Format("2006-01-02 15:04:05"+"\r\n"))) } return }
if v := r.FormValue("insert"); v != "" { _, err := db.Exec("INSERT INTO test_t (a) VALUES (?)", v) if err != nil { w.Write([]byte(err.Error())) return } w.Write([]byte("OK")) return }
w.Write([]byte("NO")) })
http.ListenAndServe(":10888", nil) }
|
该段代码是简单的通过API来访问数据库内容。
我们的重点不是API部分,而是我们开启了一个数据库的连接,然后我们将数据库文件删除后会有什么现象发生呢?
首先,我们先向插入一些数据。
1 2
| $ curl "http://127.0.0.1:10888/test?insert=ddd" OK
|
然后我们查询一下。
1 2 3 4 5 6 7 8
| $ curl "http://127.0.0.1:10888/test?query=xxx"
ddd | 2018-09-10 16:24:32 ddd | 2018-09-10 16:24:33 ddd | 2018-09-10 16:24:34 ddd | 2018-09-10 16:24:34 ddd | 2018-09-10 16:24:35 ddd | 2018-09-10 16:24:35
|
我们看到已经能查到数据。
我们看到,当前目录已经生成了一个test.sqlite
文件。
我们看看该进程在系统的状态。

我看看到,该进程打开了.../go/src/test.sqlite
数据库文件。
现在,我们将文件删了。
1 2 3 4
| $ rm -rf test.sqlite
$ ls ...
|
我们再次调取查询接口。
1 2 3 4 5 6 7 8
| $ curl "http://127.0.0.1:10888/test?query=xxx"
ddd | 2018-09-10 16:24:32 ddd | 2018-09-10 16:24:33 ddd | 2018-09-10 16:24:34 ddd | 2018-09-10 16:24:34 ddd | 2018-09-10 16:24:35 ddd | 2018-09-10 16:24:35
|
我们发现数据还在。
我们再试试插入数据。
1 2
| $ curl "http://127.0.0.1:10888/test?insert=ddd" attempt to write a readonly database
|
此时,我们发现,没有写入成功,而是显示了一个错误。
我们在查看一下进程打开的文件。

我们发现居然是一样的。
1
| ls: .../go/src/test.sqlite: No such file or directory
|
我们发现并没有该文件。
try & try
我们先不忙者下结论,我们再看看另外一种写法。
那就是每次使用完数据库就关闭连接,下次使用再打开。
代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| package main
import ( "database/sql" "net/http" "time"
_ "github.com/mattn/go-sqlite3" )
func getDB() *sql.DB { db, err := sql.Open("sqlite3", "file:test.sqlite") if err != nil { panic(err) } return db }
func main() { db := getDB() _, err := db.Exec(`CREATE TABLE IF NOT EXISTS test_t( a TEXT, create_at TIMESTAMP default CURRENT_TIMESTAMP );`)
if err != nil { panic(err) }
db.Close()
http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) { db := getDB() defer db.Close() if r.FormValue("query") != "" { rows, err := db.Query("SELECT * FROM test_t") if err != nil { w.Write([]byte(err.Error())) return }
for rows.Next() { var a string var t time.Time err := rows.Scan(&a, &t) if err != nil { w.Write([]byte(err.Error())) return }
w.Write([]byte(a + " | " + t.Format("2006-01-02 15:04:05"+"\r\n"))) } return }
if v := r.FormValue("insert"); v != "" { _, err := db.Exec("INSERT INTO test_t (a) VALUES (?)", v) if err != nil { w.Write([]byte(err.Error())) return } w.Write([]byte("OK")) return }
w.Write([]byte("NO")) })
http.ListenAndServe(":10888", nil) }
|
我们先跑起来。
1
| $ go run test-sqlite2.go
|
向其中插入一些数据。
1 2
| $ curl "http://127.0.0.1:10888/test?insert=ddd" OK
|
查询一下。
1 2 3 4 5
| $ curl "http://127.0.0.1:10888/test?query=xxx" ddd | 2018-09-10 23:29:42 ddd | 2018-09-10 23:29:43 ddd | 2018-09-10 23:29:43 ddd | 2018-09-10 23:29:44
|
我们将数据库删除。
再次查询。
1 2
| $ curl "http://127.0.0.1:10888/test?query=xxx" no such table: test_t
|
此时,给我们表现是,该表已经不存在了。
我们再查看一下数据库,发现数据库居然还在。
1 2 3 4 5 6
| $ sqlite3 test.sqlite SQLite version 3.19.3 2017-06-27 16:48:08 Enter ".help" for usage hints. sqlite > select count(*) from sqlite_master; 0 sqlite >
|
虽然库文件依然存在(应该是查询是sql.Open
创建的),但其中的数据,表都不存在了。
so
通过两种方法的对比,我们比较容易想到, 在第一种操作中,由于我们打开一个数据库连接,始终没有关闭,即使数据库删除后,其引用仍然存在,文件还没留在内存中。后一种,我们每次操作完成后会关闭数据库,导致数据库直接被删除了。
但,如果数据库很大,内存放不了那么多怎么办?
所以,这个还有待验证,先记录一下,留个坑给自己。