记录一个mysql大量数据快速插入的方法

本文介绍了一种通过批量插入数据来提高MySQL(MyISAM引擎)插入性能的方法,并通过实验对比单条插入与批量插入的效率差异。

经过数月的历练与开发,终于在这个月手上负责的一个项目即将交付了,看着这一两个月让我偶感压力的繁杂的事情一件件处理完毕,原本动荡的心静也渐渐平静了些许。此时再回过头过去看那些困扰我N多个日夜的某些问题其实也不过如此。有些事情自己无法改变,只能乐观地坦然地面对。面对充满未知的未来努力让自己保持每一天都快乐才是王道,哈哈!

在本周将一个需要处理上千万数据量的项目部署到UAT环境后,发现数据插入到mysql(mariadb)的时间太慢了,尽管已经根据项目需要将原来的innodb存储引擎修改为了更适合大量数据插入和查询效率更高的myisam引擎。通过对代码再次进行分析后发现是由于代码对于数据插入到mysql的过程中耗费了大量的时间,由于myisam又不支持事务,不能设置批量插入,思量后决定将之前的单条insert语句修改为多条数据的insert语句或许性能就会有所提升,正好趁着今天放假将这块的代码先写个DEMO给测试下。

测试代码

测试代码如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

@RequestMapping(value = "mysql-test")
@RestController
public class MysqlTestController {

    @Autowired
    private JdbcTemplate jdbcTemplate;


    @GetMapping(value = "mulTest")
    public String mulTest(Integer size, @RequestParam(defaultValue = "false") boolean sharding) {
        initUserInfoTable();
        BlockingQueue<UserInfo> userInfoQueue = initUserInfoQueue(size);
        return size + " 条记录插入,耗时" + processQueue(userInfoQueue, sharding);
    }

    /**
     * 初始化userInfo表
     */
    private void initUserInfoTable() {
        String userInfoTableDdlStr = "create table if not exists  `userinfo` (\n" +
                "  `userId` int(11) DEFAULT NULL,\n" +
                "  `userName` varchar(255) DEFAULT NULL,\n" +
                "  `gender` varchar(255) DEFAULT NULL,\n" +
                "  `phone` varchar(255) DEFAULT NULL,\n" +
                "  `address` varchar(255) DEFAULT NULL,\n" +
                "  `birthDay` bigint(20) DEFAULT NULL,\n" +
                "  `school` varchar(255) DEFAULT NULL,\n" +
                "  `hobby` varchar(255) DEFAULT NULL,\n" +
                "  KEY `index_userid` (`userId`)\n" +
                ") ENGINE=MyISAM DEFAULT CHARSET=utf8";
        jdbcTemplate.execute(userInfoTableDdlStr);
    }

    private long processQueue(BlockingQueue<UserInfo> userInfoQueue, boolean sharding) {
        int mulSize = 200;
        long beginTime = System.currentTimeMillis();
        try {
            if (sharding) {
                while (!userInfoQueue.isEmpty()) {
                    UserInfo userInfo = userInfoQueue.take();
                    StringBuffer singleInserSql = new StringBuffer();
                    singleInserSql.append("INSERT INTO `userInfo` (`userId`, `userName`, `gender`, `phone`, `address`, `birthDay`, `school`, `hobby`) VALUES (?,?,?,?,?,?,?,?)");
                    jdbcTemplate.update(singleInserSql.toString(), new Object[]{
                            userInfo.getUserId(), userInfo.getUserName(), userInfo.getGender(), userInfo.getPhone(), userInfo.getAddress(), userInfo.getBirthDay().getTime() / 1000, userInfo.getSchool(), userInfo.getHobby()
                    });
                }
            } else {
                String insertSqlStr = genMulFixSqlStr(mulSize);
                List<UserInfo> userInfoList = new ArrayList<>();
                while (!userInfoQueue.isEmpty()) {
                    UserInfo userInfo = userInfoQueue.take();
                    userInfoList.add(userInfo);
                    if (userInfoList.size() == mulSize) {
                        Object[] params = getMulParams(userInfoList, userInfoList.size());
                        jdbcTemplate.update(insertSqlStr, params);
                        userInfoList.clear();
                    }
                }
                //插入剩余的数据
                if (!userInfoList.isEmpty()) {
                    String restInsertSql = genMulFixSqlStr(userInfoList.size());
                    Object[] params = getMulParams(userInfoList, userInfoList.size());
                    jdbcTemplate.update(restInsertSql, params);
                    userInfoList.clear();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        return endTime - beginTime;
    }

    private Object[] getMulParams(List<UserInfo> userInfoList, int mulSize) {
        Object[] params = new Object[mulSize * 8];
        for (int i = 0; i < mulSize; i++) {
            UserInfo userInfo = userInfoList.get(i);
            params[8 * i] = userInfo.getUserId();
            params[8 * i + 1] = userInfo.getUserName();
            params[8 * i + 2] = userInfo.getGender();
            params[8 * i + 3] = userInfo.getPhone();
            params[8 * i + 4] = userInfo.getAddress();
            params[8 * i + 5] = userInfo.getBirthDay().getTime() / 1000;
            params[8 * i + 6] = userInfo.getSchool();
            params[8 * i + 7] = userInfo.getHobby();
        }
        return params;
    }

    private String genMulFixSqlStr(int mulSize) {
        StringBuffer mulInserSql = new StringBuffer();
        mulInserSql.append("INSERT INTO `userInfo` (`userId`, `userName`, `gender`, `phone`, `address`, `birthDay`, `school`, `hobby`) VALUES (?,?,?,?,?,?,?,?)");
        for (int i = 1; i < mulSize; i++) {
            mulInserSql.append(",(?,?,?,?,?,?,?,?)");
        }
        return mulInserSql.toString();
    }

    private BlockingQueue<UserInfo> initUserInfoQueue(Integer size) {
        BlockingQueue<UserInfo> userInfoQueue = new LinkedBlockingQueue<>();
        for (int i = 0; i <= size; i++) {
            UserInfo userInfo = new UserInfo();
            userInfo.setAddress("shanghai" + i);
            userInfo.setBirthDay(new Date());
            userInfo.setGender(i % 2 == 0 ? "male" : "famale");
            userInfo.setHobby("programing,basketball,reading,driving");
            userInfo.setPhone("15700" + i);
            userInfo.setSchool("njCoolege");
            userInfo.setUserId(i);
            userInfo.setUserName("ping" + i);

            userInfoQueue.add(userInfo);
        }
        return userInfoQueue;
    }


    private class UserInfo {
        private Integer userId;
        private String userName;
        private String gender;
        private String phone;
        private String address;
        private Date birthDay;
        private String school;
        private String hobby;

        public Integer getUserId() {
            return userId;
        }

        public void setUserId(Integer userId) {
            this.userId = userId;
        }

        public String getUserName() {
            return userName;
        }

        public void setUserName(String userName) {
            this.userName = userName;
        }

        public String getGender() {
            return gender;
        }

        public void setGender(String gender) {
            this.gender = gender;
        }

        public String getPhone() {
            return phone;
        }

        public void setPhone(String phone) {
            this.phone = phone;
        }

        public String getAddress() {
            return address;
        }

        public void setAddress(String address) {
            this.address = address;
        }

        public Date getBirthDay() {
            return birthDay;
        }

        public void setBirthDay(Date birthDay) {
            this.birthDay = birthDay;
        }

        public String getSchool() {
            return school;
        }

        public void setSchool(String school) {
            this.school = school;
        }

        public String getHobby() {
            return hobby;
        }

        public void setHobby(String hobby) {
            this.hobby = hobby;
        }
    }


}

快速敲完后,立即试试插入1万条数据试试效率如何

分别访问:A.http://localhost:8080/mysql-test/mulTest?size=10000&sharding=true

B.http://localhost:8080/mysql-test/mulTest?size=10000&sharding=true

两个URL后发现之前的单条插入的sql语句插入10000条时间花费了9721多毫秒

而修改后的多条SQL插入10000条数据仅花费了373毫秒


通过简单的对比测试后,可知多条数据批量插入的效率确实比单条数据插入的数据高上许多倍,比想象之中的预期还要高上许多。

有了上面的实践数据作为有力的理论后盾,得了,为了项目能更好地上线,再在家里加个班吧,写代码去了。加油!!!

总结

最后还是再总结一下

对于需要向mysql中插入大量数据可通过如下方式进行:

1.将表的存储引擎修改为myisam

2.插入数据时采用批量插入方式进行



<think> 我们正在讨论MySQL单个表快速插入数据方法。根据之前的引用,我们可以总结出几种方法:使用多个值的插入语句、LOAD DATA INFILE语句、事务和调整MySQL配置参数。另外,引用[3]提到了表分区可能提升插入效率,但需要分析其机制。 参考引用[1]:MySQL高效批量插入大量数据方法,包括: - 使用多个值的插入语句(例如:INSERT INTO table (col1, col2) VALUES (val1, val2), (val3, val4), ...) - 使用LOAD DATA INFILE语句(从文件导入) - 使用事务(将多个插入操作包裹在一个事务中,减少提交次数) - 调整MySQL配置参数(如增大innodb_buffer_pool_size, innodb_log_file_size等) 参考引用[2]:导出表数据方法,但用户的问题是插入,所以主要参考导出部分可能不直接相关,但我们可以忽略。 参考引用[3]:表分区对插入效率的影响。分区表在插入时,需要先确定记录所在的分区,然后进行插入,并且可能涉及移动数据(如果更新导致分区改变)。因此,分区表在插入时可能会带来额外的开销,但对于大数据量的表,分区可以提升查询性能,而对插入效率的影响需要具体情况具体分析。 因此,针对单个表快速插入数据,我们可以推荐以下方法: 1. 使用批量插入(Multiple Values Insert):一次性插入多行数据,减少SQL语句解析和网络开销。 2. 使用LOAD DATA INFILE:从文本文件导入数据,这是MySQL最快的数据导入方式。 3. 使用事务:将多个插入操作放在一个事务中,减少事务提交的次数(因为每次提交都会写磁盘日志)。 4. 调整配置参数:如增大innodb_buffer_pool_size(缓存池大小)、innodb_log_file_size(日志文件大小)、innodb_flush_log_at_trx_commit(设置为0或2,但会影响数据安全性)等。 5. 禁用索引和约束:在插入大量数据前暂时禁用索引(非唯一索引)和约束(如外键),插入完成后再重建索引和约束。 6. 表分区:虽然引用[3]提到插入时可能会增加一些开销,但对于非常大的表,分区可以分散写入压力(例如按时间分区,将数据分散到不同的物理子表),但需要根据实际场景测试。 注意:表分区在插入时,由于需要确定数据所在的分区,可能会比非分区表稍慢,但如果是按时间分区且数据刚好按时间顺序插入,则可能提升效率(因为每次插入只需要操作一个分区,减少索引大小等)。所以分区对插入效率的影响取决于数据分布和分区键的选择。 因此,在回答中,我们将重点放在前5种方法上,表分区作为可选方案但要说明其适用场景。 我们按照以下结构组织回答: ### MySQL单个表快速插入数据方法 1. **批量插入(Multiple Values Insert)** 一次性插入多行数据,减少SQL语句解析和网络开销。 ```sql INSERT INTO table_name (col1, col2) VALUES (val1, val2), (val3, val4), ...; ``` 建议每批插入1000-10000行,避免单个SQL语句过大[^1]。 2. **LOAD DATA INFILE(文件导入)** 从文本文件直接导入数据,速度最快,适用于大数据量导入。 ```sql LOAD DATA INFILE 'file_path' INTO TABLE table_name; ``` 注意:文件路径需在MySQL服务器上,且需要FILE权限[^1]。 3. **事务控制(Transaction)** 将多个插入操作包裹在一个事务中,减少提交次数。 ```sql START TRANSACTION; INSERT INTO table_name ...; INSERT INTO table_name ...; ... COMMIT; ``` 避免每插入一行就提交一次[^1]。 4. **禁用索引和约束** 对于大表插入,暂时禁用非唯一索引和外键约束,插入完成后再重建。 ```sql -- 禁用索引(MyISAM) ALTER TABLE table_name DISABLE KEYS; -- 插入数据 ... -- 启用索引 ALTER TABLE table_name ENABLE KEYS; -- InnoDB表在插入完成后重建索引 ALTER TABLE table_name DROP INDEX index_name; -- 插入数据 ... ALTER TABLE table_name ADD INDEX index_name(...); ``` 禁用唯一索引需谨慎(可能破坏唯一性),外键约束可临时关闭[^1]。 5. **调整MySQL配置参数** - `innodb_buffer_pool_size`:增大缓冲池(建议为系统内存的50-70%) - `innodb_log_file_size`:增大日志文件大小(如1GB) - `innodb_flush_log_at_trx_commit`:设置为0或2(牺牲部分持久性换取速度) - `innodb_autoinc_lock_mode`:设置为2(交叉锁模式,提高自增主键并发插入效率) 修改参数需重启MySQL或在运行时调整(部分支持动态)[^1]。 6. **表分区(Partitioning)** 分区表将数据分散到不同物理子表中,可提升查询和维护效率,但对插入效率影响需具体分析: - **优点**:如果数据按分区键有序插入,可分散写入压力; - **缺点**:插入时需计算分区位置,增加开销(尤其随机分区键)。 适用于数据量极大且有明显分区键(如时间)的场景[^3]。 ### 方法选择建议 | **场景** | **推荐方法** | |------------------------------|----------------------------------| | 少量多次插入 | 批量插入 + 事务 | | 超大数据导入(GB级) | LOAD DATA INFILE | | 高并发插入 | 调整配置参数 + 事务 | | 历史数据归档 | 表分区 + 批量插入 | 注意:以上方法可组合使用(如批量插入+事务+禁用索引)。 最后,我们生成相关问题: §§相关问题§§ 1. 如何优化MySQL配置以实现更高的插入性能? 2. LOAD DATA INFILE导入数据时如何跳过错误行? 3. 分区表在插入数据时有哪些注意事项? 4. 为什么禁用索引能提升插入速度? </think> ### MySQL单个表快速插入数据方法总结 针对单表大数据插入场景,以下方法可显著提升效率(综合引用[1][3]的核心方案): --- #### 1. **批量插入(Multiple Values Insert)** **原理**:单条SQL插入多行数据,减少SQL解析和网络开销。 ```sql -- 传统单行插入(慢) INSERT INTO orders (id, amount) VALUES (1, 100); INSERT INTO orders (id, amount) VALUES (2, 200); -- 批量插入(推荐) INSERT INTO orders (id, amount) VALUES (1, 100), (2, 200), (3, 300); -- 单次插入1000~10000行为最佳实践[^1] ``` **效果**:比单行插入快 **10~100倍**。 --- #### 2. **LOAD DATA INFILE(文件导入)** **原理**:绕过SQL解析,直接从文件加载数据(最快方法)。 ```sql -- 从CSV文件导入(需FILE权限) LOAD DATA INFILE '/path/to/orders.csv' INTO TABLE orders FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n'; ``` **优势**: - 速度比SQL插入快 **20倍以上** - 支持跳过错误行(`IGNORE n LINES`)[^1][^2] --- #### 3. **事务包裹批量操作** **原理**:减少磁盘I/O次数,避免每次插入自动提交。 ```sql START TRANSACTION; -- 开启事务 INSERT INTO orders ...; -- 批量插入1 INSERT INTO orders ...; -- 批量插入2 COMMIT; -- 统一提交[^1] ``` **配置增强**: ```ini innodb_flush_log_at_trx_commit = 0 -- 提交时不刷盘(风险与性能权衡) innodb_autoinc_lock_mode = 2 -- 提升自增ID并发性[^1] ``` --- #### 4. **分区表优化(Partitioning)** **原理**:将大表拆分为物理子表,分散写入负载。 ```sql -- 创建按时间范围分区的表 CREATE TABLE orders ( id INT, order_date DATE ) PARTITION BY RANGE (YEAR(order_date)) ( PARTITION p2023 VALUES LESS THAN (2024), PARTITION p2024 VALUES LESS THAN (2025) ); -- 插入时自动路由到对应分区[^3] ``` **适用场景**: - 时间序列数据(如日志、订单) - 分区键与插入数据强相关时效率最佳[^3] --- #### 5. **预处理与索引优化** **关键操作**: 1. **禁用非关键索引** ```sql ALTER TABLE orders DISABLE KEYS; -- 插入前禁用索引 -- 执行批量插入... ALTER TABLE orders ENABLE KEYS; -- 插入后重建索引(比逐行维护快) ``` 2. **使用内存表暂存** ```sql INSERT INTO orders_in_memory -- 先写入内存表 SELECT ... FROM source_data; INSERT INTO orders -- 批量导入磁盘表 SELECT * FROM orders_in_memory; ``` --- ### 性能对比与选择建议 | **方法** | 插入100万行耗时 | 适用场景 | |-------------------------|------------------|--------------------------| | `LOAD DATA INFILE` | ~5秒 | 超大数据迁移(GB级) | | 事务+批量插入 | ~30秒 | 程序生成数据实时写入 | | 单行插入 | >10分钟 | 极小数据量 | | 分区表+批量插入 | ~25秒 | 时间序列数据高频写入[^3] | > **提示**: > - 优先组合使用(如:事务+批量插入+索引禁用) > - 超大数据导入首选 `LOAD DATA INFILE` > - 分区表需匹配数据分布特征[^3] ---
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水中加点糖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值