限制已准备的最大语句数

时间:2019-01-02 10:14:37

标签: mysql go

问题

我编写了一个应用程序,用于将BigQuery中的数据同步到MySQL数据库中。我尝试每3小时分批插入大约10-20k行(每批最多插入10个项目)。由于某种原因,当我尝试将这些行向上插入MySQL时,我收到以下错误:

不能创建超过max_prepared_stmt_count条语句:

  

错误1461:最多只能创建max_prepared_stmt_count条语句   (当前值:2000)

我的“相关代码”

// ProcessProjectSkuCost receives the given sku cost entries and sends them in batches to upsertProjectSkuCosts()
func ProcessProjectSkuCost(done <-chan bigquery.SkuCost) {
    var skuCosts []bigquery.SkuCost
    var rowsAffected int64
    for skuCostRow := range done {
        skuCosts = append(skuCosts, skuCostRow)

        if len(skuCosts) == 10 {
            rowsAffected += upsertProjectSkuCosts(skuCosts)
            skuCosts = []bigquery.SkuCost{}
        }
    }
    if len(skuCosts) > 0 {
        rowsAffected += upsertProjectSkuCosts(skuCosts)
    }
    log.Infof("Completed upserting project sku costs. Affected rows: '%d'", rowsAffected)
}

// upsertProjectSkuCosts inserts or updates ProjectSkuCosts into SQL in batches
func upsertProjectSkuCosts(skuCosts []bigquery.SkuCost) int64 {
    // properties are table fields
    tableFields := []string{"project_name", "sku_id", "sku_description", "usage_start_time", "usage_end_time",
        "cost", "currency", "usage_amount", "usage_unit", "usage_amount_in_pricing_units", "usage_pricing_unit",
        "invoice_month"}
    tableFieldString := fmt.Sprintf("(%s)", strings.Join(tableFields, ","))

    // placeholderstring for all to be inserted values
    placeholderString := createPlaceholderString(tableFields)
    valuePlaceholderString := ""
    values := []interface{}{}
    for _, row := range skuCosts {
        valuePlaceholderString += fmt.Sprintf("(%s),", placeholderString)
        values = append(values, row.ProjectName, row.SkuID, row.SkuDescription, row.UsageStartTime,
            row.UsageEndTime, row.Cost, row.Currency, row.UsageAmount, row.UsageUnit,
            row.UsageAmountInPricingUnits, row.UsagePricingUnit, row.InvoiceMonth)
    }
    valuePlaceholderString = strings.TrimSuffix(valuePlaceholderString, ",")

    // put together SQL string
    sqlString := fmt.Sprintf(`INSERT INTO
        project_sku_cost %s VALUES %s ON DUPLICATE KEY UPDATE invoice_month=invoice_month`, tableFieldString, valuePlaceholderString)
    sqlString = strings.TrimSpace(sqlString)

    stmt, err := db.Prepare(sqlString)
    if err != nil {
        log.Warn("Error while preparing SQL statement to upsert project sku costs. ", err)
        return 0
    }

    // execute query
    res, err := stmt.Exec(values...)
    if err != nil {
        log.Warn("Error while executing statement to upsert project sku costs. ", err)
        return 0
    }

    rowsAffected, err := res.RowsAffected()
    if err != nil {
        log.Warn("Error while trying to access affected rows ", err)
        return 0
    }

    return rowsAffected
}

// createPlaceholderString creates a string which will be used for prepare statement (output looks like "(?,?,?)")
func createPlaceholderString(tableFields []string) string {
    placeHolderString := ""
    for range tableFields {
        placeHolderString += "?,"
    }
    placeHolderString = strings.TrimSuffix(placeHolderString, ",")

    return placeHolderString
}

我的问题:

当我立即执行准备好的语句时,为什么要打{{1​​}}(请参见函数max_prepared_stmt_count)?

我只能想象这是某种并发性,它会在准备和执行所有这些语句之间创建大量准备好的语句。另一方面,我不明白为什么会有这么多的并发,因为upsertProjectSkuCosts中的通道是大小为20的缓冲通道。

1 个答案:

答案 0 :(得分:2)

您需要关闭upsertProjectSkuCosts()内的语句(或重新使用它-参见本文结尾)。

当您调用db.Prepare()时,将从内部连接池中建立连接(或者,如果没有任何空闲连接,则创建新连接)。然后在该连接上准备该语句(如果在调用stmt.Exec()时该连接不是空闲的,则在另一个连接上也准备该语句)。 因此,这将在数据库中为该连接创建一条语句。该语句不会神奇地消失-连接中具有多个准备好的语句是完全有效的。 Golang 可以看到stmt超出范围,看到它需要进行某种清理,然后再进行清理,但是Golang不需要(就像它不会为您关闭文件一样)诸如此类)。因此,您需要自己使用stmt.Close()进行此操作。当您调用stmt.Close()时,驱动程序将向数据库服务器发送命令,告诉它不再需要该语句。

最简单的方法是在defer stmt.Close()之后的err检查之后添加db.Prepare()

您还可以做的是,准备一次语句并将其提供给upsertProjectSkuCosts使用(通过将stmt传递到upsertProjectSkuCosts或将upsertProjectSkuCosts设为func的结构,因此该结构可以具有stmt的属性。如果这样做,您应该呼叫stmt.Close()-因为您不再创建新的语句,因此您将重新使用现有的语句。

另请参见Should we also close DB's .Prepare() in Golang?https://groups.google.com/forum/#!topic/golang-nuts/ISh22XXze-s