Sql server 使用goroutines丢失数据
我正在编写一个AWS lambda代码来查询RDS表,将其转换为JSON并返回它。但是我在JSON中看到的所有记录都不是SQL查询返回的记录。假设我从表中查询1500条记录,但每次JSON中只有1496到1500条记录(少0-5条记录)。我怀疑我把Sql server 使用goroutines丢失数据,sql-server,go,aws-lambda,goroutine,Sql Server,Go,Aws Lambda,Goroutine,我正在编写一个AWS lambda代码来查询RDS表,将其转换为JSON并返回它。但是我在JSON中看到的所有记录都不是SQL查询返回的记录。假设我从表中查询1500条记录,但每次JSON中只有1496到1500条记录(少0-5条记录)。我怀疑我把sync.WaitGroup搞砸了 下面是SQL Server查询 SELECT TOP 1500 * FROM IMBookingApp.dbo.BA_Contact__c WHERE ContactId > 0 下面是我的代码 // Con
sync.WaitGroup
搞砸了
下面是SQL Server查询
SELECT TOP 1500 * FROM IMBookingApp.dbo.BA_Contact__c
WHERE ContactId > 0
下面是我的代码
// Convert the rows object to slice of objects for every row
func parseRow(rows *sql.Rows, totalColumns int) []string {
receiver := make([]string, totalColumns)
is := make([]interface{}, len(receiver))
for i := range is {
is[i] = &receiver[i]
}
err := rows.Scan(is...)
if err != nil {
fmt.Println("Error reading rows: " + err.Error())
}
TotalRecordsInParseRowfunction++
return receiver
}
// Query the given table and return JSON response
func queryTable(conn *sql.DB, query string) (string, error) {
// Query Table
rows, err := conn.Query(query)
if err != nil {
fmt.Println("DATABASE ERROR:", err)
return "", errors.New("DATABASE ERROR:" + err.Error())
}
println("Rows:", rows)
defer rows.Close()
// Get the column names
columns, err := rows.Columns()
// fmt.Println("columns", columns)
if err != nil {
fmt.Println("DATABASE ERROR:", err)
return "", errors.New("DATABASE ERROR:" + err.Error())
}
totalColumns := len(columns)
var resp []map[string]string // Declare the type of final response which will be used to create JSON
var waitgroup sync.WaitGroup
// Iterate over all the rows returned
for rows.Next() {
waitgroup.Add(1)
TotalRecordsCount++
row := parseRow(rows, totalColumns)
go func() {
// Create a map of the row
respRow := map[string]string{} // Declare the type of each row of response
for count := range row {
respRow[columns[count]] = row[count]
}
// fmt.Println("\n\nrespRow", respRow)
resp = append(resp, respRow)
TotalRecordsAppendedCount++
waitgroup.Done()
}()
}
waitgroup.Wait()
// If no rows are returned
if len(resp) == 0 {
fmt.Println("MESSAGE: No records are available")
return "", errors.New("MESSAGE: No records are available")
}
// Create JSON
respJSON, _ := json.Marshal(resp)
fmt.Println("Response", string(respJSON))
fmt.Println("\n--------------Summary---------------")
fmt.Println("TotalRecordsInParseRowfunction", TotalRecordsInParseRowfunction)
fmt.Println("TotalRecordsCount", TotalRecordsCount)
fmt.Println("TotalRecordsAppendedCount", TotalRecordsAppendedCount)
fmt.Println("Object Length", len(resp))
return string(respJSON), nil // Return JSON
}
下面是我得到的输出摘要
--------------Summary---------------
TotalRecordsInParseRowfunction 1500
TotalRecordsCount 1500
TotalRecordsAppendedCount 1500
Object Length 1496
你的代码很活泼。多个goroutine正在写入
resp
,没有任何互斥,因此会丢失数据
您可以在其周围添加互斥锁解锁。但是,goroutine中的代码并不保证它自己的goroutine,因为它是一个简单的映射添加。在goroutine中处理这些代码会容易得多,并且可能在没有goroutine调度开销的情况下运行得更快。除非你打算在这个goroutine中有更多的逻辑,否则我建议你删除它
下面是关于可能发生的事情的更多信息:首先,在当前版本的go中,goroutine只有在调用某些库函数时才会向其他goroutine屈服。看看代码,您的goroutines不太可能会让步。因为您已经观察到数据丢失(这意味着存在竞争条件),所以您可能有多个内核
比赛在这里进行:
resp = append(resp, respRow)
在没有互斥的情况下,一个goroutine可以查看resp
,看到它可以写入其n
th元素。另一个goroutine(在单独的核心上运行)也可以做同样的事情,并在那里成功地写入。但是第一个goroutine仍然认为元素是空的,所以覆盖它,并更新resp
。发生这种情况时,将丢失一个元素
如果您在这段代码中添加互斥,您将强制所有goroutine按顺序运行,因为它们实际上没有做任何其他事情。此外,由于goroutine的执行顺序是随机的,因此最终将得到随机排序的
resp
。简而言之,这是您应该顺序执行代码的实例之一。当您添加一个互斥锁(在queryTable
中的for
循环之前声明)时会发生什么情况,该互斥锁在resp=append(resp,respRow)
之前锁定,然后在它之后解锁?可能有些项目在那里丢失了我以前没有使用过互斥锁。让我试试。谢谢你的回答。我已经在代码中添加了互斥,但仍有一些行丢失。您可以在这里看到我的更改-您正在为每一行创建一个单独的互斥,因此您并没有真正阻止竞争。将互斥声明移到for循环之外。是。您在goroutine中所做的工作量非常小,并且您拥有一个共享资源。如果创建3k goroutine,则创建3k*2k堆栈空间,并且所有goroutine都在等待锁定。你可以使用频道,但你所做的工作量仍然很小。测量并查看。“3K行”不是对资源本身的估计。为了使3K行中每个行的多个包的并发处理具有意义,独立于所有其他包处理此类包所花费的CPU周期量必须相当大;否则就不会有性能提升,也可能会有净性能损失。这可能不是直接花费CPU时间(比如对这些行中的数据进行运算),而是像查询外部数据源等。关键是这些活动本身必须是可并行的。