LOADING

加载过慢请开启缓存 浏览器默认开启

数据库批量Insert优化

一、合并多条Insert语句

提高批量插入语句执行效率的办法之一就是将多条INSERT合并为一条,以下代码可以自动合并INSERT语句,自动忽略非INSERT语句,可以设置是否过滤注释。

package org.example;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.StrBuilder;

import java.io.*;
import java.nio.charset.StandardCharsets;


/**
 * 主要功能:将多个insert语句合并成一个
 * 参考了这个代码 cn.hutool.core.text.csv.CsvParser
 */
public class Main {

    public static void main(String[] args) {

        // x个insert语句合并为一个
        final int x = 50;
        // 移除注释
        final boolean rmComments = true;

        try (// 输入文件是可以是包含非Insert语句的混合文件,例如由Navicat、Dbeaver等软件的转储的SQL文件(包含结构和数据)
             BufferedReader reader = FileUtil.getReader("D://mysqlintellij.sql", StandardCharsets.UTF_8);
             BufferedWriter writer = FileUtil.getWriter("D://mysqlintellij_.sql", StandardCharsets.UTF_8, false)) {

            int n = 0;
            SqlParser sqlParser = new SqlParser(reader, rmComments);
            // 上一个select语句的关键部分
            String preInsertStatementKey = null;
            String preSql = null;
            boolean preSqlIsInsert = false;

            while (sqlParser.hasNext()) {
                SqlParser.SQLPart sqlPart = sqlParser.readOneSql();
                String sql = sqlPart.getSql();
                // System.out.println(sqlPart.getSqlNo());
                // 判断当前sql是不是insert语句
                boolean isInsertSql = sql != null && !sql.isEmpty() && sql.toLowerCase().contains("insert");
                if (isInsertSql) {
                    String insertStatementKey = sql.substring(sql.toLowerCase().indexOf("insert"), sql.toLowerCase().indexOf("values") + 6);
                    if (preSqlIsInsert) {

                        boolean sameInsert = insertStatementKey.equals(preInsertStatementKey);
                        if (n < x - 1 && sameInsert) {
                            preSql = preSql.replace(";", ",");
                        }

                        if (n > 0) {
                            preSql = preSql.substring(preSql.indexOf("VALUES") + 6);
                        }

                        writer.append(preSql);
                        if (n == x - 1 || !sameInsert) {
                            n = 0;
                            writer.append("\n");
                        } else {
                            n++;
                        }
                        // System.out.println(n);
                    } else {
                        writer.append(preSql).append("\n");
                    }
                    preSqlIsInsert = true;
                    preInsertStatementKey = insertStatementKey;
                } else {

                    if (preSqlIsInsert) {
                        if (n > 0) {
                            preSql = preSql.substring(preSql.indexOf("VALUES") + 6);
                        }
                        writer.append(preSql).append("\n");
                        n = 0;
                    } else if (preSql != null) {
                        writer.append(preSql).append("\n");
                    }
                }
                preSql = sql;

            }
            System.out.println("完成");
        } catch (Exception e) {
            e.printStackTrace();
        }


    }


    static class SqlParser {
        private final Reader reader;

        /**
         * 当前sql编号
         */
        private long sqlNo = -1;
        /**
         * 前一个特殊分界字符
         */
        private int preChar = -1;
        private int prePreChar = -1;

        private final Buffer buf = new Buffer(IoUtil.DEFAULT_LARGE_BUFFER_SIZE);
        private final boolean rmComments;
        /**
         * 在多行注释中
         */
        boolean inMComment = false;
        /**
         * 在单行注释中
         */
        boolean inLComment = false;

        private boolean finished;
        /**
         * 当前读取SQL
         */
        private final StrBuilder currentField = new StrBuilder(512);

        SqlParser(Reader reader, boolean rmComments) {
            this.reader = reader;
            this.rmComments = rmComments;
        }

        public boolean hasNext() {
            return !finished;
        }

        static class SQLPart {
            private long sqlNo;
            private String sql;

            public SQLPart(long sqlNo, String sql) {
                this.sqlNo = sqlNo;
                this.sql = sql;
            }

            public long getSqlNo() {
                return sqlNo;
            }

            public void setSqlNo(long sqlNo) {
                this.sqlNo = sqlNo;
            }

            public String getSql() {
                return sql;
            }

            public void setSql(String sql) {
                this.sql = sql;
            }
        }

        public SQLPart readOneSql() {

            final Buffer buf = this.buf;

            int copyLen = 0; // 拷贝长度

            final StrBuilder currentField = this.currentField;

            while (true) {
                if (!buf.hasRemaining()) {
                    // 此Buffer读取结束,开始读取下一段
                    if (copyLen > 0) {
                        buf.appendTo(currentField, copyLen);
                        // 此处无需mark,read方法会重置mark
                    }
                    if (buf.read(this.reader) < 0) {
                        // CSV读取结束
                        finished = true;

                        if (currentField.hasContent() || preChar == ';') {

                            // 剩余部分作为一个SQL返回
                            return new SQLPart(++sqlNo, currentField.toStringAndReset());
                        }
                        break;
                    }

                    // 重置
                    copyLen = 0;
                }

                final char c = buf.get();


                if (rmComments) {
                    // 多行注释
                    if (c == '*' && preChar == '/') {
                        copyLen = 0;
                        inMComment = true;
                    }
                    if (inMComment) {
                        if (c == '/' && preChar == '*') {
                            inMComment = false;
                        }
                        buf.mark();
                        prePreChar = preChar;
                        preChar = c;
                        continue;
                    }

                    // 单行注释
                    if (c == ' ' && preChar == '-' && prePreChar == '-') {
                        copyLen = 0;
                        inLComment = true;
                    }
                    if (inLComment) {
                        if (c == '\n' || c == '\r') {
                            inLComment = false;
                        }
                        buf.mark();
                        prePreChar = preChar;
                        preChar = c;
                        continue;
                    }
                }

                if (c == ';') {
                    if (copyLen > 0) {
                        buf.appendTo(currentField, copyLen);
                    }
                    buf.mark();
                    prePreChar = preChar;
                    preChar = c;

                    return new SQLPart(++sqlNo, currentField.append(c).toStringAndReset());
                } else if (this.preChar == ';' && (c == '\r' || c == '\n' || c == ' ')) {
                    buf.mark();
                } else {
                    copyLen++;
                    prePreChar = preChar;
                    preChar = c;
                }

            }

            sqlNo++;
            return null;
        }

    }

    private static class Buffer implements Serializable {
        private static final long serialVersionUID = 1L;

        final char[] buf;

        /**
         * 标记位置,用于读数据
         */
        private int mark;
        /**
         * 当前位置
         */
        private int position;
        /**
         * 读取的数据长度,一般小于buf.length,-1表示无数据
         */
        private int limit;

        Buffer(int capacity) {
            buf = new char[capacity];
        }

        /**
         * 是否还有未读数据
         *
         * @return 是否还有未读数据
         */
        public final boolean hasRemaining() {
            return position < limit;
        }

        /**
         * 读取到缓存<br>
         * 全量读取,会重置Buffer中所有数据
         *
         * @param reader {@link Reader}
         */
        int read(Reader reader) {
            int length;
            try {
                length = reader.read(this.buf);
            } catch (IOException e) {
                throw new IORuntimeException(e);
            }
            this.mark = 0;
            this.position = 0;
            this.limit = length;
            return length;
        }

        /**
         * 先获取当前字符,再将当前位置后移一位<br>
         * 此方法不检查是否到了数组末尾,请自行使用{@link #hasRemaining()}判断。
         *
         * @return 当前位置字符
         * @see #hasRemaining()
         */
        char get() {
            return this.buf[this.position++];
        }

        /**
         * 标记位置记为下次读取位置
         */
        void mark() {
            this.mark = this.position;
        }

        /**
         * 将数据追加到{@link StrBuilder},追加结束后需手动调用{@link #mark()} 重置读取位置
         *
         * @param builder {@link StrBuilder}
         * @param length  追加的长度
         * @see #mark()
         */
        void appendTo(StrBuilder builder, int length) {
            builder.append(this.buf, this.mark, length);
        }
    }

}