限制最大准备好的语句计数

Posted

技术标签:

【中文标题】限制最大准备好的语句计数【英文标题】:Limit max prepared statement count 【发布时间】:2019-01-02 10:14:37 【问题描述】:

问题

我编写了一个将 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

我的问题:

当我立即执行准备好的语句时,为什么我会点击max_prepared_stmt_count(参见函数upsertProjectSkuCosts)?

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

【问题讨论】:

【参考方案1】:

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

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

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

您还可以做的,是准备一次声明并使其可用于upsertProjectSkuCosts(通过将stmt 传递给upsertProjectSkuCosts 或使upsertProjectSkuCosts 成为结构的函数,因此结构可以拥有stmt 的属性)。如果您这样做,您应该致电stmt.Close() - 因为您不再创建新语句,而是重复使用现有语句。

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

【讨论】:

以上是关于限制最大准备好的语句计数的主要内容,如果未能解决你的问题,请参考以下文章

更新语句的 JDBC 占位符限制的解决方法失败

Kohana 准备好的语句或查询构建?

SQL的语句最大长度限制多少字符

如何在意图“Intent.EXTRA_ALLOW_MULTIPLE”上设置最大文件选择计数。用户最多可以选择一定的限制?

PDO 准备语句限制不起作用 [重复]

PHP 的 PDO 可以限制为单个查询吗?