Mysql
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Mysql

Hiệu suất của các câu lệnh Chèn trong MySQL trong Java:Các câu lệnh được chuẩn bị ở chế độ hàng loạt so với chèn một lần với nhiều giá trị

JDBC chỉ đơn giản là một tiêu chuẩn truy cập cơ sở dữ liệu Java SE cung cấp các giao diện tiêu chuẩn, vì vậy bạn không thực sự bị ràng buộc với một triển khai JDBC cụ thể. Trình kết nối Java MySQL (Connector / J) là một triển khai của các giao diện JDBC chỉ dành cho cơ sở dữ liệu MySQL. Theo kinh nghiệm, tôi đang tham gia vào một dự án sử dụng lượng lớn dữ liệu bằng MySQL và chúng tôi chủ yếu thích MyISAM hơn cho dữ liệu có thể được tạo:nó cho phép đạt được hiệu suất cao hơn nhiều khi giao dịch bị mất, nhưng nói chung, MyISAM nhanh hơn, nhưng InnoDB đáng tin cậy hơn.

Tôi đã tự hỏi về hiệu suất của các câu lệnh INSERT khoảng một năm trước và tìm thấy mã thử nghiệm cũ sau đây trong kệ mã của tôi (xin lỗi, nó hơi phức tạp và nằm ngoài phạm vi câu hỏi của bạn). Đoạn mã dưới đây chứa các ví dụ về 4 cách chèn dữ liệu thử nghiệm:

  • đơn INSERT s;
  • theo đợt INSERT s;
  • hàng loạt thủ công INSERT (không bao giờ sử dụng nó - nó nguy hiểm);
  • và cuối cùng là số lượng lớn đã chuẩn bị INSERT ).

Nó sử dụng TestNG với tư cách là người chạy và sử dụng một số di sản mã tùy chỉnh như:

  • runWithConnection() phương thức - đảm bảo rằng kết nối được đóng hoặc đưa trở lại nhóm kết nối sau khi lệnh gọi lại được thực thi (nhưng đoạn mã dưới đây sử dụng chiến lược không đáng tin cậy của câu lệnh đóng - ngay cả khi không có try / finally để giảm mã);
  • IUnsafeIn<T, E extends Throwable> - giao diện gọi lại tùy chỉnh cho các phương thức chấp nhận một tham số duy nhất nhưng có khả năng tạo ra một ngoại lệ thuộc loại E, như:void handle(T argument) throws E; .
package test;

import test.IUnsafeIn;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import static java.lang.String.format;
import static java.lang.String.valueOf;
import static java.lang.System.currentTimeMillis;

import core.SqlBaseTest;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

public final class InsertVsBatchInsertTest extends SqlBaseTest {

    private static final int ITERATION_COUNT = 3000;

    private static final String CREATE_TABLE_QUERY = "CREATE TABLE IF NOT EXISTS ttt1 (c1 INTEGER, c2 FLOAT, c3 VARCHAR(5)) ENGINE = InnoDB";
    private static final String DROP_TABLE_QUERY = "DROP TABLE ttt1";
    private static final String CLEAR_TABLE_QUERY = "DELETE FROM ttt1";

    private static void withinTimer(String name, Runnable runnable) {
        final long start = currentTimeMillis();
        runnable.run();
        logStdOutF("%20s: %d ms", name, currentTimeMillis() - start);
    }

    @BeforeSuite
    public void createTable() {
        runWithConnection(new IUnsafeIn<Connection, SQLException>() {
            @Override
            public void handle(Connection connection) throws SQLException {
                final PreparedStatement statement = connection.prepareStatement(CREATE_TABLE_QUERY);
                statement.execute();
                statement.close();
            }
        });
    }

    @AfterSuite
    public void dropTable() {
        runWithConnection(new IUnsafeIn<Connection, SQLException>() {
            @Override
            public void handle(Connection connection) throws SQLException {
                final PreparedStatement statement = connection.prepareStatement(DROP_TABLE_QUERY);
                statement.execute();
                statement.close();
            }
        });
    }

    @BeforeTest
    public void clearTestTable() {
        runWithConnection(new IUnsafeIn<Connection, SQLException>() {
            @Override
            public void handle(Connection connection) throws SQLException {
                final PreparedStatement statement = connection.prepareStatement(CLEAR_TABLE_QUERY);
                statement.execute();
                statement.close();
            }
        });
    }

    @Test
    public void run1SingleInserts() {
        withinTimer("Single inserts", new Runnable() {
            @Override
            public void run() {
                runWithConnection(new IUnsafeIn<Connection, SQLException>() {
                    @Override
                    public void handle(Connection connection) throws SQLException {
                        for ( int i = 0; i < ITERATION_COUNT; i++ ) {
                            final PreparedStatement statement = connection.prepareStatement("INSERT INTO ttt1 (c1, c2, c3) VALUES (?, ?, ?)");
                            statement.setInt(1, i);
                            statement.setFloat(2, i);
                            statement.setString(3, valueOf(i));
                            statement.execute();
                            statement.close();
                        }
                    }
                });
            }
        });
    }

    @Test
    public void run2BatchInsert() {
        withinTimer("Batch insert", new Runnable() {
            @Override
            public void run() {
                runWithConnection(new IUnsafeIn<Connection, SQLException>() {
                    @Override
                    public void handle(Connection connection) throws SQLException {
                        final PreparedStatement statement = connection.prepareStatement("INSERT INTO ttt1 (c1, c2, c3) VALUES (?, ?, ?)");
                        for ( int i = 0; i < ITERATION_COUNT; i++ ) {
                            statement.setInt(1, i);
                            statement.setFloat(2, i);
                            statement.setString(3, valueOf(i));
                            statement.addBatch();
                        }
                        statement.executeBatch();
                        statement.close();
                    }
                });
            }
        });
    }

    @Test
    public void run3DirtyBulkInsert() {
        withinTimer("Dirty bulk insert", new Runnable() {
            @Override
            public void run() {
                runWithConnection(new IUnsafeIn<Connection, SQLException>() {
                    @Override
                    public void handle(Connection connection) throws SQLException {
                        final StringBuilder builder = new StringBuilder("INSERT INTO ttt1 (c1, c2, c3) VALUES ");
                        for ( int i = 0; i < ITERATION_COUNT; i++ ) {
                            if ( i != 0 ) {
                                builder.append(",");
                            }
                            builder.append(format("(%s, %s, '%s')", i, i, i));
                        }
                        final String query = builder.toString();
                        final PreparedStatement statement = connection.prepareStatement(query);
                        statement.execute();
                        statement.close();
                    }
                });
            }
        });
    }

    @Test
    public void run4SafeBulkInsert() {
        withinTimer("Safe bulk insert", new Runnable() {
            @Override
            public void run() {
                runWithConnection(new IUnsafeIn<Connection, SQLException>() {
                    private String getInsertPlaceholders(int placeholderCount) {
                        final StringBuilder builder = new StringBuilder("(");
                        for ( int i = 0; i < placeholderCount; i++ ) {
                            if ( i != 0 ) {
                                builder.append(",");
                            }
                            builder.append("?");
                        }
                        return builder.append(")").toString();
                    }

                    @SuppressWarnings("AssignmentToForLoopParameter")
                    @Override
                    public void handle(Connection connection) throws SQLException {
                        final int columnCount = 3;
                        final StringBuilder builder = new StringBuilder("INSERT INTO ttt1 (c1, c2, c3) VALUES ");
                        final String placeholders = getInsertPlaceholders(columnCount);
                        for ( int i = 0; i < ITERATION_COUNT; i++ ) {
                            if ( i != 0 ) {
                                builder.append(",");
                            }
                            builder.append(placeholders);
                        }
                        final int maxParameterIndex = ITERATION_COUNT * columnCount;
                        final String query = builder.toString();
                        final PreparedStatement statement = connection.prepareStatement(query);
                        int valueIndex = 0;
                        for ( int parameterIndex = 1; parameterIndex <= maxParameterIndex; valueIndex++ ) {
                            statement.setObject(parameterIndex++, valueIndex);
                            statement.setObject(parameterIndex++, valueIndex);
                            statement.setObject(parameterIndex++, valueIndex);
                        }
                        statement.execute();
                        statement.close();
                    }
                });
            }
        });
    }

}

Hãy xem các phương thức được chú thích bằng chú thích @Test:chúng thực sự thực thi INSERT các câu lệnh. Ngoài ra, vui lòng xem CREATE_TABLE_QUERY hằng số:trong mã nguồn, nó sử dụng InnoDB tạo ra các kết quả sau trên máy của tôi có cài đặt MySQL 5.5 (MySQL Connector / J 5.1.12):

InnoDB
Single inserts: 74148 ms
Batch insert: 84370 ms
Dirty bulk insert: 178 ms
Safe bulk insert: 118 ms

Nếu bạn thay đổi CREATE_TABLE_QUERY InnoDB sang MyISAM, bạn sẽ thấy hiệu suất tăng đáng kể:

MyISAM
Single inserts: 604 ms
Batch insert: 447 ms
Dirty bulk insert: 63 ms
Safe bulk insert: 26 ms

Hy vọng điều này sẽ hữu ích.

CẬP NHẬT:

Đối với cách thứ 4, bạn phải tùy chỉnh đúng cách max_allowed_packet trong mysql.ini ([mysqld] ) đủ lớn để hỗ trợ các gói thực sự lớn.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Làm cách nào để viết lại thành công mã mysql-php cũ bằng các hàm mysql_ * không dùng nữa?

  2. Chọn dữ liệu giữa hai ngày?

  3. truy vấn mysql để cập nhật trường thành max (trường) + 1

  4. Làm thế nào để khởi động máy chủ MySQL từ dòng lệnh trên Mac OS Lion?

  5. SQLSTATE [HY000]:Lỗi chung:1298 Múi giờ không xác định hoặc không chính xác:cửa sổ 'UTC'