2014-10-19 17 views
7

Tôi đang gặp vấn đề khi Bắt đầu chơi độc đáo với SQLite, tôi đã làm điều đó trong quá khứ mà không gặp sự cố nhưng đã mất một thời gian và tôi không nhớ mình đã làm gì làm việc đúng cách. Tôi đang sử dụng Go cùng với gói mattn/go-sqlite3 để xử lý và chèn rất nhiều dữ liệu vào cơ sở dữ liệu SQLite nhưng bằng cách nào đó, Go luôn kết thúc việc ăn tất cả RAM của tôi cho đến khi cuối cùng thoát với mã trạng thái lỗi. Chỉ cần chắc chắn rằng tôi đã cô lập các vấn đề bộ nhớ đói để SQLite tôi đã viết các chương trình đơn giản sau đây để kiểm tra nó ra:SQLite 3 không giải phóng bộ nhớ trong Golang

package main 

import (
    "database/sql" 
    "fmt" 
    "log" 
    _ "github.com/mattn/go-sqlite3" 
) 

func main() { 
    db, err := sql.Open("sqlite3", "./test.db"); if err != nil { 
     log.Fatal(err) 
    }; defer db.Close() 

    ddl := ` 
     PRAGMA automatic_index = ON; 
     PRAGMA cache_size = 32768; 
     PRAGMA cache_spill = OFF; 
     PRAGMA foreign_keys = ON; 
     PRAGMA journal_size_limit = 67110000; 
     PRAGMA locking_mode = NORMAL; 
     PRAGMA page_size = 4096; 
     PRAGMA recursive_triggers = ON; 
     PRAGMA secure_delete = ON; 
     PRAGMA synchronous = NORMAL; 
     PRAGMA temp_store = MEMORY; 
     PRAGMA journal_mode = WAL; 
     PRAGMA wal_autocheckpoint = 16384; 

     CREATE TABLE IF NOT EXISTS "user" (
      "id" TEXT, 
      "username" TEXT, 
      "password" TEXT 
     ); 

     CREATE UNIQUE INDEX IF NOT EXISTS "id" ON "user" ("id"); 
    ` 

    _, err = db.Exec(ddl); if err != nil { 
     log.Fatal(err) 
    } 

    queries := map[string]*sql.Stmt{} 

    queries["user"], _ = db.Prepare(`INSERT OR REPLACE INTO "user" VALUES (?, ?, ?);`); if err != nil { 
     log.Fatal(err) 
    }; defer queries["user"].Close() 

    tx, err := db.Begin(); if err != nil { 
     log.Fatal(err) 
    } 

    for i := 0; i < 10000000; i++ { 
     user := map[string]string{ 
      "id": string(i), 
      "username": "foo", 
      "password": "bar", 
     } 

     _, err := tx.Stmt(queries["user"]).Exec(user["id"], user["username"], user["password"]); if err != nil { 
      log.Fatal(err) 
     } 

     if i % 32768 == 0 { 
      tx.Commit() 
      db.Exec(`PRAGMA shrink_memory;`) 

      tx, err = db.Begin(); if err != nil { 
       log.Fatal(err) 
      } 

      fmt.Println(i) 
     } 
    } 

    tx.Commit() 
} 

Khi tôi chạy đoạn mã trên, Gò ăn hơn 100 MiB bộ nhớ mỗi giây mà không cần mỗi phát hành bất kỳ, sau một phút hoặc lâu hơn nó kết thúc tiêu thụ 6/7 GiB và sau đó quá trình bị giết. Tôi đã thử các biến thể có và không xác định các PRAGMA SQLite nhưng không có may mắn.

Theo các PRAGMA được xác định, SQLite không bao giờ nên sử dụng nhiều hơn 128 MiB RAM.

Tôi đã thực hiện bất kỳ sai lầm nào hoặc có vấn đề gì với mattn/go-sqlite3 hoặc Go GC không?


Profiling với davecheney/profile theo these instructions mang lại sản lượng không quá hữu ích này:

[email protected]:~/Go/src$ go tool pprof --text ./test /tmp/profile102098478/mem.pprof 
Adjusting heap profiles for 1-in-4096 sampling rate 
Total: 0.0 MB 
    0.0 100.0% 100.0%  0.0 100.0% runtime.allocm 
    0.0 0.0% 100.0%  0.0 100.0% database/sql.(*DB).Exec 
    0.0 0.0% 100.0%  0.0 100.0% database/sql.(*DB).conn 
    0.0 0.0% 100.0%  0.0 100.0% database/sql.(*DB).exec 
    0.0 0.0% 100.0%  0.0 100.0% github.com/mattn/go-sqlite3.(*SQLiteDriver).Open 
    0.0 0.0% 100.0%  0.0 100.0% github.com/mattn/go-sqlite3._Cfunc_sqlite3_threadsafe 
    0.0 0.0% 100.0%  0.0 100.0% main.main 
    0.0 0.0% 100.0%  0.0 100.0% runtime.cgocall 
    0.0 0.0% 100.0%  0.0 100.0% runtime.gosched0 
    0.0 0.0% 100.0%  0.0 100.0% runtime.main 
    0.0 0.0% 100.0%  0.0 100.0% runtime.newextram 

Đây là chỉ dành riêng cho 1000000 lặp và bộ nhớ vẫn mọc như không có tomorow.

Tôi cũng thử mã tương tự trên hai MacBook Pros, cả hai đều chạy phiên bản mới nhất của Go từ brew (1.3.1), một trong số đó sử dụng bộ nhớ đứng đầu 10 GiB và trung bình 2 GiB tiêu thụ RAM khác. Điều này trông giống như một hành vi kỳ quặc, tôi có thể làm gì để theo dõi sự khác biệt và sửa lỗi hogging bộ nhớ?

+0

Chạy đi phiên bản go1.2.1 linux/amd64. –

+0

Bạn có thể hài hước và xóa bản đồ 'người dùng' không? Chỉ cần vượt qua các giá trị và xem có bao nhiêu thay đổi sử dụng bộ nhớ của bạn? –

+0

@SimonWhitehead: Chỉ cần làm, không thể nói đó là bất kỳ khác nhau - về việc sử dụng bộ nhớ lặp đi lặp lại 1081344 đã đạt 5 GiB rồi. –

Trả lời

6

Tôi không thể tạo lại kết quả của bạn. Nó sử dụng khoảng 100 MiB bộ nhớ.

$ go version 
go version devel +7ab3adc146c9 Sun Oct 19 10:33:50 2014 -0700 linux/amd64 
$ sqlite3 --version 
3.8.2 2013-12-06 14:53:30 27392118af4c38c5203a04b8013e1afdb1cebd0d 
$ go get -v github.com/mattn/go-sqlite3 
github.com/mattn/go-sqlite3 (download) 
github.com/mattn/go-sqlite3 
$ go run simple.go 
0 
32768 
65536 
<SNIP> 
9928704 
9961472 
9994240 
$ 

Thời gian chạy.MemStats ghi lại số liệu thống kê về bộ cấp phát bộ nhớ Go. Nó không bao gồm bộ nhớ được quản lý bởi SQLite. Ví dụ, ở phần cuối của chương trình,

var ms runtime.MemStats 
runtime.ReadMemStats(&ms) 
fmt.Println(
    ms.Alloc,  // bytes allocated and still in use 
    ms.TotalAlloc, // bytes allocated (even if freed) 
    ms.Sys,  // bytes obtained from system (sum of XxxSys below) 
    ms.Mallocs, // number of mallocs 
    ms.Frees,  // number of frees 
) 

Output:

12161440 7953059928 18757880 160014535 159826250 

Và nó cũng hoạt động trên Go 1.4 Beta 1

 
$ go version 
go version go1.4beta1 linux/amd64 
+1

Tôi chỉ chạy cùng một mã trên máy tính xách tay làm việc Mac của tôi (thử nghiệm ban đầu của tôi là dưới Ubuntu 14.04) và sử dụng bộ nhớ cũng đã đi từ 4.5GiB đến hơn 14GiB trước quá trình cuối cùng đã kết thúc. Tôi đang chạy Go 1.3.1. –

+0

Cảm ơn bạn đã thử BTW, ít nhất là vào cuối của bạn, mã hoạt động giống như nó. Bây giờ tôi chỉ cần tìm ra lý do. –

+0

@AlixAxel: Bạn nhận được kết quả gì từ 'runtime.MemStats'? Xem câu trả lời đã sửa đổi của tôi cho kết quả của tôi. – peterSO

3

này đã được thảo luận trên GitHub tại chiều dài. Tôi gửi bài ở đây để giúp

https://github.com/mattn/go-sqlite3/issues/157

Giải pháp đã được tìm thấy, vấn đề phải làm với tuyên bố này:

_, err := tx.Stmt(queries["user"]).Exec(user["id"], user["username"], user["password"]); 

này được ném đi các hàng và các mã sau không bao giờ gọi Close() .

Phiên bản này của chương trình thử nghiệm hoạt động chính xác

package main 

import (
    "database/sql" 
    "fmt" 
    _ "github.com/mattn/go-sqlite3" 
    "log" 
) 

func main() { 
    db, err := sql.Open("sqlite3", "./test.db") 
    if err != nil { 
     log.Fatal(err) 
    } 
    defer db.Close() 

    ddl := ` 
     PRAGMA automatic_index = ON; 
     PRAGMA cache_size = 32768; 
     PRAGMA cache_spill = OFF; 
     PRAGMA foreign_keys = ON; 
     PRAGMA journal_size_limit = 67110000; 
     PRAGMA locking_mode = NORMAL; 
     PRAGMA page_size = 4096; 
     PRAGMA recursive_triggers = ON; 
     PRAGMA secure_delete = ON; 
     PRAGMA synchronous = NORMAL; 
     PRAGMA temp_store = MEMORY; 
     PRAGMA journal_mode = WAL; 
     PRAGMA wal_autocheckpoint = 16384; 

     CREATE TABLE IF NOT EXISTS "user" (
      "id" TEXT, 
      "username" TEXT, 
      "password" TEXT 
     ); 

     CREATE UNIQUE INDEX IF NOT EXISTS "id" ON "user" ("id"); 
    ` 

    _, err = db.Exec(ddl) 
    if err != nil { 
     log.Fatal(err) 
    } 

    queries := map[string]*sql.Stmt{} 

    queries["user"], _ = db.Prepare(`INSERT OR REPLACE INTO "user" VALUES (?, ?, ?);`) 
    if err != nil { 
     log.Fatal(err) 
    } 
    defer queries["user"].Close() 

    tx, err := db.Begin() 
    if err != nil { 
     log.Fatal(err) 
    } 

    for i := 0; i < 10000000; i++ { 
     user := map[string]string{ 
      "id":  string(i), 
      "username": "foo", 
      "password": "bar", 
     } 

     rows, err := tx.Stmt(queries["user"]).Exec(user["id"], user["username"], user["password"]) 
     if err != nil { 
      log.Fatal(err) 
     } 
     // CLOSE ROWS HERE! 
     rows.Close() 

     if i%32768 == 0 { 
      tx.Commit() 
      db.Exec(`PRAGMA shrink_memory;`) 

      tx, err = db.Begin() 
      if err != nil { 
       log.Fatal(err) 
      } 

      fmt.Println(i) 
     } 
    } 

    tx.Commit() 
} 
+0

Điều này sẽ giải quyết sự rò rỉ thực sự, nhưng nó không còn cần thiết như các gói như đã được cố định vào tài khoản cho các biến bị bỏ qua. Tôi vẫn tò mò muốn biết tại sao một số người dùng không thể tái tạo lại suy nghĩ của vấn đề. Cảm ơn bạn đã chia sẻ trên StackOverflow. –

+0

Lưu ý rằng giải pháp được đề xuất này không hoạt động. 'rows' sẽ là một [' SQLiteResult'] (http://godoc.org/github.com/mattn/go-sqlite3#SQLiteResult) và không phải 'SQLiteRows' -' SQLiteResult' không có phương thức 'Close' : * rows.Close undefined (loại sql.Result không có trường hoặc phương thức Close) *. –

Các vấn đề liên quan