Những gì bạn có là bế tắc . Trong trường hợp xấu nhất, bạn có 15 goroutines giữ 15 kết nối cơ sở dữ liệu và tất cả 15 goroutines đó đều yêu cầu một kết nối mới để tiếp tục. Nhưng để có được một kết nối mới, người ta sẽ phải tiến lên và giải phóng một kết nối:deadlock.
Bài báo wikipedia được liên kết chi tiết phòng ngừa bế tắc. Ví dụ, một quá trình thực thi mã chỉ nên nhập một phần quan trọng (khóa tài nguyên) khi nó có tất cả các tài nguyên mà nó cần (hoặc sẽ cần). Trong trường hợp này, điều này có nghĩa là bạn sẽ phải đặt trước 2 kết nối (chính xác là 2; nếu chỉ có 1, hãy để nó và đợi), và nếu bạn có 2 kết nối đó, chỉ sau đó tiếp tục với các truy vấn. Nhưng trong Go, bạn không thể đặt trước các kết nối. Chúng được cấp phát khi cần thiết khi bạn thực thi các truy vấn.
Nói chung nên tránh mô hình này. Bạn không nên viết mã mà đầu tiên dự trữ một tài nguyên (hữu hạn) (kết nối db trong trường hợp này) và trước khi nó phát hành nó, nó yêu cầu một cái khác.
Một cách giải quyết dễ dàng là thực hiện truy vấn đầu tiên, lưu kết quả của nó (ví dụ:vào một lát Go) và khi bạn hoàn thành việc đó, sau đó tiếp tục với các truy vấn tiếp theo (nhưng cũng đừng quên đóng sql.Rows
Đầu tiên). Bằng cách này, mã của bạn không cần 2 kết nối cùng một lúc.
Và đừng quên xử lý lỗi! Tôi đã bỏ qua chúng cho ngắn gọn, nhưng bạn không nên sử dụng mã của mình.
Đây là cách nó có thể trông như thế này:
go func() {
defer wg.Done()
rows, _ := db.Query("SELECT * FROM reviews LIMIT 1")
var data []int // Use whatever type describes data you query
for rows.Next() {
var something int
rows.Scan(&something)
data = append(data, something)
}
rows.Close()
for _, v := range data {
// You may use v as a query parameter if needed
db.Exec("SELECT * FROM reviews LIMIT 1")
}
}()
Lưu ý rằng rows.Close()
nên được thực thi dưới dạng defer
để đảm bảo rằng nó sẽ được thực thi (ngay cả trong trường hợp hoảng sợ). Nhưng nếu bạn chỉ sử dụng defer rows.Close()
, điều đó sẽ chỉ được thực thi sau khi các truy vấn tiếp theo được thực thi, vì vậy nó sẽ không ngăn chặn bế tắc. Vì vậy, tôi sẽ cấu trúc lại nó để gọi nó trong một hàm khác (có thể là một hàm ẩn danh), trong đó bạn có thể sử dụng defer
:
rows, _ := db.Query("SELECT * FROM reviews LIMIT 1")
var data []int // Use whatever type describes data you query
func() {
defer rows.Close()
for rows.Next() {
var something int
rows.Scan(&something)
data = append(data, something)
}
}()
Cũng lưu ý rằng trong for
lặp lại một câu lệnh đã chuẩn bị ( sql.Stmt
) được mua bởi DB.Prepare()
có lẽ sẽ là lựa chọn tốt hơn nhiều để thực hiện cùng một truy vấn (được tham số hóa) nhiều lần.
Một tùy chọn khác là khởi chạy các truy vấn tiếp theo trong các goroutines mới để truy vấn được thực thi trong đó có thể xảy ra khi kết nối hiện đang bị khóa được giải phóng (hoặc bất kỳ kết nối nào khác bị khóa bởi bất kỳ goroutines nào khác), nhưng sau đó nếu không có đồng bộ hóa rõ ràng, bạn không có quyền kiểm soát khi họ bị hành quyết. Nó có thể trông như thế này:
go func() {
defer wg.Done()
rows, _ := db.Query("SELECT * FROM reviews LIMIT 1")
defer rows.Close()
for rows.Next() {
var something int
rows.Scan(&something)
// Pass something if needed
go db.Exec("SELECT * FROM reviews LIMIT 1")
}
}()
Để chương trình của bạn cũng phải đợi các goroutines này, hãy sử dụng WaitGroup
bạn đã có hành động:
// Pass something if needed
wg.Add(1)
go func() {
defer wg.Done()
db.Exec("SELECT * FROM reviews LIMIT 1")
}()