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

时间:2017-03-10 01:08:37

标签: java mysql multithreading oracle locking

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

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

最简单的方法是在其中插入一个循环,客户端每次都插入一条记录,直到插入所有记录。这是单线程顺序插入。

还有另一种多线程并发插入方式,允许客户端同时启动多个线程,每个线程都将一条记录插入表中。直觉上,因为这些记录是独立的,并且假设现代数据库服务器带有RAID,其中并发IO得到良好支持,它们似乎能够为多个插入获得实用且真实的并发性,因此,这种方式可以改善性能,与上述方法相比。

然而,一旦我深入了解更多细节,事实证明可能并非如此。此链接 - Multi threaded insert using ORM?表示同一个表上的插入需要锁定整个表上的每个写入。因此,每个插入只会阻止另一个插入,最终,这种方式只是另一种顺序多次插入,根本没有性能提升。

我的问题如下:

  1. 为什么大多数DB会像这样处理同一个表上的多线程插入?
  2. 为什么整个桌子上的插入锁是强制性的?
  3. 多线程更新是否与多线程插入类似?
  4. 尽管似乎处理大量插入的最佳方法是启用批量插入,但我仍然非常好奇在插入时锁定整个表的理由。

    提前致谢!

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

    经过充分的阅读和研究,它表明我的问题实际上是错误的。真实的是一个插入不会同时阻止另​​一个插入。(至少对Oracle来说是这样)。

3 个答案:

答案 0 :(得分:3)

这个答案需要了解数据库,这超出了简单答案的范围。既然你问过Oracle:

Oracle不会以您认为的方式锁定整个表。在插入过程中,存在对表结构的锁定(即,有人不能在插入中插入列),但在数据级别,没有锁定。这意味着您可以在单个表上有许多并发插入。更新(在Oracle中)类似。但是,在这种情况下,正在更新的数据上存在行锁定。因此,您可以在同一个表上进行许多并发更新;但不是在同一排。

已经说过,多线程插入以这种方式加载大量数据。为此,Oracle提供了一种替代方法,即直接路径加载。在这种方法中,我们加载行集,而不是逐行(慢 - 慢)。并不是单个插入物很慢;恰恰相反,它们非常快。但即使每插入0.1ms,当你需要加载100M行时,这是2.7小时!由于基于集合的方法允许数据库执行并行性,而不是手动“本地化”多线程方法 因此,为了让您了解可以可以做什么,我在大约10分钟内加载了大约60亿行(大约1 TB的数据)。 最后,数据加载通常受CPU限制;不受IO限制。

答案 1 :(得分:1)

  

最简单的方法是在其中插入一个循环,客户端每次都插入一条记录,直到插入所有记录。这是单线程顺序插入。

即使使用单线程操作,暂停自动提交(可以通过启动事务来完成),在批处理中插入大量条目,然后提交更改比以1比1进行插入更有效

  

...假设现代数据库服务器附带RAID,其中并发IO受到良好支持

实际上,硬件级别可能没有并发IO这样的东西。 IO请求可以像网络接口中的数据包一样被序列化,即使我们认为它们是与服务器的多个并发连接。但是,它是由多个应用程序线程排队IO请求,从而最大化IO总线。

另外,RAID通常也是串行IO,通常甚至比单个设备慢 - 特别是当我们谈论写入时。例如,RAID5速度很慢,大多数高性能集群都使用RAID50来尝试将性能提升到足够的水平。

  

为什么大多数DB会像这样处理同一个表上的多线程插入?

这在很大程度上取决于数据库类型,可能与它如何保持表的有序性有关。大多数插入都写入数据表的末尾(或争取空行),因此多个线程将满足相同的磁盘空间,从而使插入序列有效。

  

为什么必须对整个表格进行插入锁定?

不是。同样,这在很大程度上取决于数据库如何实现插入。

  

多线程更新是否与多线程插入类似?

我不这么认为。更新发生在表中的不同位置,尽管可能会使用区域锁定,如果索引字段更新,肯定会越过索引锁定。

你的问题应该是“如何最大限度地提高插入带宽”。我(和其他人)已经提到批量插入作为第一步。您还需要确保使用数据库连接池 - 这对于单线程数据库操作也很重要。池化意味着您可以同时使用多个连接,并且不必为每个数据库事务创建连接。那里有许多数据库连接池库。我们使用HikariCP

希望这有帮助。

答案 2 :(得分:0)

没有比编写演示来证明理论更好的了。

我已经制定了一个下面的演示来比较单线程顺序插入(没有批处理)和针对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所说,上述方法是一种“本土化的”#34;处理大量插入的最佳方法是批量插入。(加载行集)