【问题标题】:Batch insert with table that has many columns using Anorm使用 Anorm 批量插入具有多列的表
【发布时间】:2014-07-04 11:25:56
【问题描述】:

我正在尝试使用 Anorm(在播放框架 2.3.1 中)批量插入 MySQL 数据库表。我正在构建的应用程序除了需要批量数据插入外,还有一个标准的 Web 前端,我想尝试将逻辑保留在同一个软件堆栈上。

插入只进入相同的几个表。

一次插入的行数将达到数百甚至数千,我预计由于异常/mysql/其他限制,我可能需要在某个时候限制插入的行数。

我使用的 MySQL 驱动是 mysql-connector-java - 5.1.31

下面是一个精简的用例。

使用表:

CREATE TABLE table1
(
  col1    INTEGER   NOT NULL,
  col2    BIGINT,
  col3    VARCHAR(255)
); 

还有scala代码:

import play.api.Play.current
import play.api.db.DB
import anorm._ 

object TestInserts {

  DB.withConnection("spo") { implicit conn => 

    val theInserts = Seq(
       Seq[NamedParameter]('val1 -> 1, 'val2 -> Some(1L), 'val3 -> Some("One"))
      ,Seq[NamedParameter]('val1 -> 2, 'val2 -> Some(2L), 'val3 -> Some("Two"))
      ,Seq[NamedParameter]('val1 -> 3, 'val2 -> Some(3L), 'val3 -> Some("Three"))
    )

    val insertBatchSQL = BatchSql( SQL("insert into table1 (col1, col2, col3) values ({val1}, {val2}, {val3})"), theInserts)  

    insertBatchSQL.execute

  } 

}

我收到以下错误

java.sql.SQLException: Parameter index out of range (1 > number of parameters, which is 0).
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1094)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:997)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:983)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:928)
at com.mysql.jdbc.PreparedStatement.checkBounds(PreparedStatement.java:3688)
at com.mysql.jdbc.PreparedStatement.setInternal(PreparedStatement.java:3670)
at com.mysql.jdbc.PreparedStatement.setInternal(PreparedStatement.java:3715)
at com.mysql.jdbc.PreparedStatement.setInt(PreparedStatement.java:3659)
at com.jolbox.bonecp.PreparedStatementHandle.setInt(PreparedStatementHandle.java:828)
at anorm.ToStatement$intToStatement$.set(ToStatement.scala:164)
at anorm.ToStatement$intToStatement$.set(ToStatement.scala:163)
...

我查看了测试批处理插入 https://github.com/playframework/playframework/blob/master/framework/src/anorm/src/test/scala/anorm/BatchSqlSpec.scala 的 play 框架中的测试类,据我所知它应该可以工作。

任何关于如何解决这个问题或者我是否应该以不同的方式解决这个问题的建议都会很棒。

【问题讨论】:

    标签: mysql scala anorm


    【解决方案1】:

    我会选择选项 B。我对 BatchSql 不是很熟悉,因为上次我检查它只是按顺序执行大量查询,这非常慢。我建议将所有内容聚合到一个查询中。执行具有一千个插入的单个查询比执行一千个单个插入要快一些,但要快得多。

    为方便起见,假设您有Seq

    case class Test(val1: Int, val2: Option[Long], val3: Option[String])
    

    然后你可以像这样构建你的查询:

    val values: Seq[Test] = Seq(....)
    
    /* Index your sequence for later, to map to inserts and parameters alike */
    val indexedValues = values.zipWithIndex
    
    /* Create the portion of the insert statement with placeholders, each with a unique index */
    val rows = indexValues.map{ case (value, i) =>
        s"({val1_${i}}, {val2_${i}}, {val3_${i}})"
    }.mkString(",")
    
    /* Create the NamedParameters for each `value` in the sequence, each with their unique index in the token, and flatten them together */
    val parameters = indexedValues.flatMap{ case(value, i) =>
        Seq(
            NamedParameter(s"val1_${i}" -> value.val1),
            NamedParameter(s"val2_${i}" -> value.val2),
            NamedParameter(s"val3_${i}" -> value.val3)
        ) 
    }
    
    /* Execute the insert statement, applying the aggregated parameters */
    SQL("INSERT INTO table1 (col1, col2, col3) VALUES " + rows)
        .on(parameters: _ *)
        .executeInsert()
    

    注意事项:

    在继续之前,您必须检查values 是否为非空,否则会生成无效的 SQL 语句。

    根据您插入的行数和列数,创建准备好的语句的标记解析器最终会从要解析的大量标记(以及字符串大小)减慢速度。在几百行几列之后,我注意到了这一点。这可以在一定程度上得到缓解。由于 Scala 是一种强类型语言,IntLong 不会对 SQL 注入构成威胁。您可以为这些列使用字符串插值/连接来准备 SQL 语句,并通常将不安全的列与NamedParameter 绑定。这将减少需要解析的令牌数量。

    【讨论】:

    • 如果 anorm BatchSql 一次只执行一个语句,它就违背了我首先选择它的原因,即由于网络访问次数减少等原因而提高了性能。所以这个创造性的解决方案可能是要走的路.....
    • 我上次在 2.2 上尝试过。我认为这应该是一种方便的方式来重新运行相同的查询,而无需重新编写任何内容。
    • 刚刚实施了这种方法,在几百行上一切正常。
    • 谢谢。这对我来说效果很好。我必须调整它的语法才能让它在 Play 中工作! 2.4.x... 将 .executeInsert() 更改为 .execute() 并将 NamedParameter(s"val1_${i}" -> value.val1) 更改为 NamedParameter(s"val1_${i}", value.val1)
    • 谢谢。使我的导入速度更快。就像@Wil 一样,我为此做了 2 次编辑以在 2.4.x 中工作。我还将-> 更改为,,但我将.executeInsert() 更改为.executeInsert(SqlParser.scalar[Long] *),因为它返回多个ID。但请注意,如果您将其与 ON DUPLICATE KEY UPDATE 一起使用,则会为更新而不是插入的行返回不正确的 ID。不确定这是 MySql 问题还是异常。
    【解决方案2】:

    BatchSql 上的一些问题已在 12 天前修复(在 2.3.1 中向后移植):https://github.com/playframework/playframework/pull/3087。它应该可以使用它。

    【讨论】:

    • 感谢您的链接,上面的代码在 play 2.3.1 上运行并出现错误。看起来 BatchSql 目前正在变化中,2.3.2 版本可能会解决它。
    • 如果你已经有 2.3.1,你可能需要清理本地 repo,以确保它得到更新的工件。
    【解决方案3】:

    试试这个:

      def save(types: List[RoomType]): Unit = {
         DB.withTransaction { implicit c =>
            val update = SQL("insert into TABLE(col1, col2, col3) values ({col1}, {col2}, {col3})")
            val batch = (update.asBatch /: types)(
        (sql, _type) => sql.addBatchParams(_type.id, _type.name, _type.lang))
        batch.execute
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-19
      • 2019-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多