java (linux mysql) 面做Batch insert很慢,20w条数据要10多分钟。
最后这个表 不过20万条数据。(表是innodb)如果单独插入表 ,是很快的 。什么原因 ,有什么方法可以加快速度?

解决方案 »

  1.   

    以下内容摘自mysql中文文档:
    7.2.16. INSERT语句的速度
    插入一个记录需要的时间由下列因素组成,其中的数字表示大约比例:连接:(3) 
    发送查询给服务器:(2) 
    分析查询:(2) 
    插入记录:(1x记录大小) 
    插入索引:(1x索引) 
    关闭:(1) 
    这不考虑打开表的初始开销,每个并发运行的查询打开。表的大小以logN (B树)的速度减慢索引的插入。加快插入的一些方法:·         如果同时从同一个客户端插入很多行,使用含多个VALUE的INSERT语句同时插入几行。这比使用单行INSERT语句快(在某些情况下快几倍)。如果你正向一个非空表添加数据,可以调节bulk_insert_buffer_size变量,使数据插入更快。参见5.3.3节,“服务器系统变量”。·         如果你从不同的客户端插入很多行,能通过INSERT DELAYED语句加快速度。参见13.2.4节,“INSERT语法”。·         用MyISAM,如果在表中没有删除的行,能在SELECT语句正在运行的同时插入行。·         当从一个文本文件装载一个表时,使用LOAD DATA INFILE。这通常比使用很多INSERT语句快20倍。参见13.2.5节,“LOAD DATA INFILE语法”。·         当表有很多索引时,有可能要多做些工作使得LOAD DATA INFILE更快些。使用下列过程:有选择地用CREATE TABLE创建表。 
    执行FLUSH TABLES语句或命令mysqladmin flush-tables。 
    使用myisamchk --keys-used=0 -rq /path/to/db/tbl_name。这将从表中取消所有索引的使用。 
    用LOAD DATA INFILE把数据插入到表中,因为不更新任何索引,因此很快。 
    如果只想在以后读取表,使用myisampack压缩它。参见15.1.3.3节,“压缩表特性”。 
    用myisamchk -r -q /path/to/db/tbl_name重新创建索引。这将在写入磁盘前在内存中创建索引树,并且它更快,因为避免了大量磁盘搜索。结果索引树也被完美地平衡。 
    执行FLUSH TABLES语句或mysqladmin flush-tables命令。 
    请注意如果插入一个空MyISAM表,LOAD DATA INFILE也可以执行前面的优化;主要不同处是可以让myisamchk为创建索引分配更多的临时内存,比执行LOAD DATA INFILE语句时为服务器重新创建索引分配得要多。也可以使用ALTER TABLE tbl_name DISABLE KEYS代替myisamchk --keys-used=0 -rq /path/to/db/tbl_name,使用ALTER TABLE tbl_name ENABLE KEYS代替myisamchk -r -q /path/to/db/tbl_name。使用这种方式,还可以跳过FLUSH TABLES。·         锁定表可以加速用多个语句执行的INSERT操作:LOCK TABLES a WRITE; 
    INSERT INTO a VALUES (1,23),(2,34),(4,33); 
    INSERT INTO a VALUES (8,26),(6,29); 
    UNLOCK TABLES; 
    这样性能会提高,因为索引缓存区仅在所有INSERT语句完成后刷新到磁盘上一次。一般有多少INSERT语句即有多少索引缓存区刷新。如果能用一个语句插入所有的行,就不需要锁定。对于事务表,应使用BEGIN和COMMIT代替LOCK TABLES来加快插入。锁定也将降低多连接测试的整体时间,尽管因为它们等候锁定最大等待时间将上升。例如:Connection 1 does 1000 insertsConnections 2, 3, and 4 do 1 insertConnection 5 does 1000 inserts如果不使用锁定,2、3和4将在1和5前完成。如果使用锁定,2、3和4将可能不在1或5前完成,但是整体时间应该快大约40%。INSERT、UPDATE和DELETE操作在MySQL中是很快的,通过为在一行中多于大约5次连续不断地插入或更新的操作加锁,可以获得更好的整体性能。如果在一行中进行多次插入,可以执行LOCK TABLES,随后立即执行UNLOCK TABLES(大约每1000行)以允许其它的线程访问表。这也会获得好的性能。INSERT装载数据比LOAD DATA INFILE要慢得多,即使是使用上述的策略。·         为了对LOAD DATA INFILE和INSERT在MyISAM表得到更快的速度,通过增加key_buffer_size系统变量来扩大 键高速缓冲区。参见7.5.2节,“调节服务器参数”。
      

  2.   

    如果单独插入表 ,是很快的  2分不到 的  我已经测试好多次
    (似乎跟索引关系不大 的 )但是在java里 就是慢很多 的
      

  3.   

    你用PreparedStatement,的executeBatch方法,每10000条提交一次,看看速度。
      

  4.   

    不是 我是用java的  batch 绑定一定性推 20w记录 
    到 db的sql是
    insert  into  a(a,b,c)  values (1,1,1),(2,2,2),(3,3,3)..........................  ;
    是这样的格式。不是一条条的。 
    如果把索引去掉,时间是1/3。 但是那些索引最终还是要加上去 ,最后还是要时间的。
    不知道在java如何加快
      

  5.   

    我这里,插1w条,一闪就好了:
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    import java.util.UUID;import test.mysql.MConnection;public class UUIDTest {
    public static void test() {
    Connection con = null;
    PreparedStatement ps = null;
    try {
    con = MConnection.getConn();
    String sql = "insert into uuidtest(uuid) values(?)";
    ps = con.prepareStatement(sql);
    for (int i = 0; i < 10000; i++) {
    String uuid = UUID.randomUUID().toString();
    System.out.println(uuid);
    ps.setString(1, uuid);
    ps.addBatch();
    }
    ps.executeBatch();
    } catch (SQLException e) {
    e.printStackTrace();
    } finally {
    if (ps != null) {
    try {
    ps.close();
    } catch (SQLException e) {
    e.printStackTrace();
    }
    }
    if (con != null) {
    try {
    con.close();
    } catch (SQLException e) {
    e.printStackTrace();
    }
    }
    }
    } public static void main(String[] args) {
    UUIDTest.test(); }
    }
      

  6.   

    语句和上差不多 ,在开发人员那边 。 我会尽快找到的  感谢楼上诸位现在我思考有没有其他方法来解决 
    我把sql语句全部放到一个文本文件里 ,之后通过数据库的
    导入文本命令来解决。           -------------------这个数度是非常亏快点哦 。 
      

  7.   

    插1w条 一闪确实晃过去 很快问题我的记录是20w , 这样估计要20多次, 为加快速度 ,我调到5w次,
    现在的现象是第一次 很快,10秒;
    但是第2次好慢 要5分
    第3次更慢估计10分,这个原因是什么 如何解决??
      

  8.   


    你第一步要做的是如果不出错更新数据。再研究怎么样速度更快。
    grandboy (资深程序员) 2010-01-10
    直接用jdbc吧!!!!!
    kjj (初级程序员) 2010-01-11
    那你需要用preparedStatement.addBatch()来解决啦
    congdepeng (初级程序员) 2010-01-11
    先不讨论你的表设计怎么样,提供一种批量处理DAO的处理方式sqlMapClientTemplate,你可以对你的数据分批执行,比如1000条执行一次,这样可以极大提高效率。
    futurep_p (初级程序员) 2010-01-11
    你写错了,修改下<statement id="excueteSql" paramenterClass="java.lang.String">
    <![CDATA[$sql$]]>
    </statement >
    lovewhzlq (CTO) 2010-01-11
    建议插入数据改为
    load data/imp/bcp等这种外部处理方式。然后更新id
    -----------最好的方式是建立一个id关系表,先读取关系,然后在java中计算对应关系,然后写文本文件。也使用load方式加载到关系表。这样就不需要一个个的update id
    kimmking (资深程序员) 2010-01-11
    可以试试存储过程,先生成id,再去更新.
    whiletrue (初级程序员) 2010-01-11
    先修改一下你的错误
    Java代码   1. <statement id="excueteSql" paramenterClass="java.lang.String">   
       2. <![CDATA[$sql$]]>   
       3. </statement >   <statement id="excueteSql" paramenterClass="java.lang.String"> 
    <![CDATA[$sql$]]> 
    </statement > ibatis是支持批量处理操作的,但是没有直接使用jdbc快,你可以通过ibatis获取dataSource,然后在获取connection然后进行批量操作,也不会浪费程序现有的连接。
    wangwenjunHi (初级程序员) 2010-01-12
    <![CDATA[$sql$]]>
    xinnn (初级程序员) 2010-01-12
    用java能做,但是不建议选择java来做这类大批量数据操作的事情。
    william_ai (初级程序员) 2010-01-12
    可以换个思路试试,把要执行的sql语句用JAVA写在文本中,然后通过数据库来跑这个脚本,不就可以实现批量更新或者批量插入了么
    max_annie (初级程序员) 2010-01-12
    同意:
    直接使用jdbc,你可以通过ibatis获取dataSource,然后在获取connection然后进行批量操作,也不会浪费程序现有的连接。
    pengjunwu (初级程序员) 2010-01-12
    ibatis:<statement id="excueteSql" paramenterClass="java.lang.String">
    <![CDATA[#?#]]>
    </statement >sql就能跑通了 ,
    咋更好就得看你具体要干啥了。。在bo层进行事务控制都是一个connection。。不用处理了。。你可以计算一下条数然后在新增更新啥的。先弄出来在干别的
    jiangnanscala (初级程序员) 2010-01-12
    引用
    我目前的做法是,在程序中通过字符串把sql拼起来。
    sqlmap中
    <statement id="excueteSql" paramenterClass="java.lang.String">
    <[CDATA[ $sql$]]>
    </statement >如果十万条记录全部用拼装SQL来实现的话,不能运行的原因是SQL长度有限,你只能拼装个几千个长度的SQL,远远不足十万的需求。首先你得确定要拼装的记录是否有其它办法得到,我碰到过一个情况,全文检索,要几十万条里检索出结果,并且全文还包括一系列其它属性,使得要精确匹配。就遇到了你那一样的问题,就是全文检索出的记录要到数据库中进一步查询,那些全文ID也是没有规律可循的。于是我使用了oracle text,就解决了这个问题。
    其次,如果你确定要使用SQL来进行查询的话,那么拼装的时候记得将拼装的SQL限制在一定长度之内(3000字?自己试吧。),再进行批处理。
    蔡华江 (架构师) 2010-01-13
    lz你也挺狠的,十万条以上的数据,拚sql,为啥这样啊?难道是出于某方面的考虑〉
    既然你使用了ibatis,那完全可以用ibatis的批处理更新啊。
    haokong (中级程序员) 2010-01-14
    批量更新数据,需要把你的数据以某种格式存放在文本文件当中,然后在执行
    Java代码   1. <statement id="excueteSql" paramenterClass="java.lang.String">   
       2. <![CDATA[#load data local infile 'F:/newNongSouCollection/newNongSouCacheSaveState/newsgn.txt' into table newsgn character set utf8  Lines Terminated By '\r\n';#]]>   
       3. </statement >   <statement id="excueteSql" paramenterClass="java.lang.String"> 
    <![CDATA[#load data local infile 'F:/newNongSouCollection/newNongSouCacheSaveState/newsgn.txt' into table newsgn character set utf8  Lines Terminated By '\r\n';#]]> 
    </statement > 
    我的格式是每个字段按照/t分隔,每段数据以\r\n结果。
    例子:
    我要插入二条信息:
    1\t我要插入\t插入完毕\r\n
    2\t我要插入\t插入完毕\r\n
    lzj0470 (中级程序员) 2010-01-16
    我不太明白你“问题补充”里说的内容。你是要一条语句对10W条起作用呢,还是你要10W条语句更新不同的行?不管怎么样,首先你得熟悉你的数据库,它们有各自对批量更新的优化,这是你第一件要做的事情。
    然后你要明白你的数据库引擎在更新数据时的锁机制,你得适应它。
    你要明白你是在用Java操作数据库,两者通过访问协议交互完成一件事情,你得做两头的优化。然后就是对现行的任何一个流行数据库都适用的。
    为你的数据库建立索引,索引以你update语句中where条件的字段建立,数据库可以通过索引较快地查找到你要更新的行。
    然后改进你的更新方式,用preparedStatement.addBatch()来批量更新,不要让你的更新事务把表锁住,这会影响你其它事务的进行,你可能得适当得1000条1000条来执行。某些数据库支持top关键字,你可以用这种原生的支持完成你的更新任务,不然,你得在程序里计数。我不想讨论是用ibatis还是用hbm,如果没有涉及任何内存中的对象,那么用JDBC直接操作更简单更有操作性。如果这个更新不是客户需求,只是一个日常维护操作,那么你可能只需要一个存储过程。很遗憾,我没有给你明确的解决方案,希望这些对你有帮助。
    jashawn (初级程序员) 2010-01-18
    用ibatis的批处理。当然你10万左右的数据也不能一批更新完,分成几个段来更新,比如1000条数据是一段。我在处理票据库存的时候就是这样做的。入库10万张票速度还上可以的。
    379548695 (中级程序员) 2010-01-19
    建议你使用存储过程,针对不同的数据库方言,使用不同的语法
    mp_juan (初级程序员) 2010-01-19
    小弟不才 没看懂你想干嘛..