有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java数据库上多线程插入(更新)和单线程顺序插入(更新)的性能比较?

让我们想象一个环境:有一个db客户端和一个db服务器。db客户端可以是Java程序或其他程序,等等;数据库服务器可以是mysql、oracle等

需求是将大量记录插入数据库服务器上的一个表中

最简单的方法是创建一个循环,客户机每次在其中插入一条记录,直到插入所有记录。这是单螺纹顺序插入

还有另一种多线程并发插入方式,它允许客户机同时启动多个线程,每个线程向表中插入一条记录。直观地说,由于这些记录是独立的,并且假设现代数据库服务器带有RAID,其中并发IO得到了很好的支持,因此它们似乎能够为多个插入获得实际和真正的并发性,因此,与上述方法相比,这种方法可以提高性能

然而,当我深入了解更多细节时,结果可能并非如此。这个链接--Multi threaded insert using ORM?表示同一个表上的插入操作需要为整个表上的每一次写入操作提供一个锁。因此,每一次插入只会阻塞另一次后续插入,最终,这种方式只是另一种类型的连续多次插入,根本没有性能提升

我的问题如下:

  1. 为什么大多数数据库都这样对待同一张表上的多线程插入
  2. 为什么整张桌子上的插入锁是强制性的
  3. 多线程更新是否与多线程插入类似

尽管处理大量插入的最佳方法似乎是启用批插入,但我仍然非常好奇在插入时锁定整个表的原因

提前谢谢

=====================================================================

经过大量的阅读和研究,我发现我的问题实际上是错的。真正的问题是,一次插入不会同时阻止另一次插入。(至少对甲骨文来说是这样)


共 (2) 个答案

  1. # 1 楼答案

    没有什么比写一个演示来证明一个理论更好的了

    我制作了一个演示,比较单线程顺序插入(无批处理)和多线程插入与Oracle之间的性能

    import com.zaxxer.hikari.HikariConfig;
    import com.zaxxer.hikari.HikariDataSource;
    
    import javax.sql.DataSource; 
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    
    /**
      * Created by boy on 14/03/17.
    */
    public class DatabasePerformanceTest {
      //constants
      private static final String DBURL ="jdbc:oracle:thin:@xxxxxxxx:1521:xxxxx";
      private static final String DBUSER = "xxx";
      private static final String DBPASS = "xxxx";
      private static final Integer INSERT_AMOUNT = 10000;
      private static final String INSERT_PERSON = "insert into Persons values(1, 'xx', 'xx', 'xxxxxxx', 'xxxxxxx')";
      //pools
      private DataSource ds;
      private ExecutorService executor;
    
    public static void main(String[] args) throws SQLException, InterruptedException {
        DatabasePerformanceTest test = new DatabasePerformanceTest();
        test.setUp();
        long begin = System.currentTimeMillis();
        //test.insertByRowByRow();
        test.insertByMultipleThreads();
        long end = System.currentTimeMillis();
        System.out.println("Time spent:" + (end - begin) + "ms");
    }
    
    private void setUp() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(DBURL);
        config.setUsername(DBUSER);
        config.setPassword(DBPASS);
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        config.addDataSourceProperty("dataSourceClassName", "oracle.jdbc.driver.OracleDriver");
        ds = new HikariDataSource(config);
        this.executor = Executors.newFixedThreadPool(128);
    }
    
    private void insertOnePerson(Connection connection) throws SQLException {
        Statement statement = null;
        try {
            statement = connection.createStatement();
            statement.execute(INSERT_PERSON);
        } finally {
            try {
                if (statement != null) {
                    statement.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Inserting one person is done.");
    }
    
    private void insertByRowByRow() throws SQLException {
        for (int i = 0; i < INSERT_AMOUNT; i++) {
            this.insertOnePerson(ds.getConnection());
        }
    }
    
    private void insertByMultipleThreads() throws InterruptedException {
        for (int i = 0; i < INSERT_AMOUNT; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        insertOnePerson(ds.getConnection());
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        executor.shutdown();
        executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
       }
    }
    

    测试后,它清楚地表明,多线程插入比单线程串行插入(无批处理)快4倍左右

    因此,链接Multi threaded insert using ORM?的第一个答案是错误的

    尽管如此,正如BobC所提到的,上述方法是一种“自主开发”的方法,处理大量插入的最佳方法是批量插入。(加载行集)

  2. # 2 楼答案

    这个答案需要了解数据库,这超出了这里简单答案的范围。既然你问起甲骨文:

    甲骨文并不像你想象的那样锁定整个表。在插入过程中,表结构本质上是一个锁(即有人不能在插入过程中删除列),但在数据级别,没有锁。这意味着您可以在单个表上有多个并发插入。更新(在Oracle中)也类似。然而,在这种情况下,正在更新的数据上有一个行锁。因此,您可以在同一个表上同时进行多个更新;但不是在同一排

    综上所述,多线程插入是而不是加载大量数据的方式。为此,Oracle提供了另一种方法,即直接路径加载。在这种方法中,我们加载一组行,而不是一行一行(慢慢地)。并不是一次插入都很慢;恰恰相反,它们的速度非常快。但即使在每次插入0.1ms时,当您必须加载100M行时,也需要2.7小时!基于集合的方法允许数据库执行并行性,而不是手动“自行开发”的多线程方法 因此,为了让您了解可以做什么,我只是在大约10分钟内加载了大约60亿行(约1 TB的数据)。 最后,数据加载通常受到CPU的限制;不受约束