Как быстро и безопасно вставлять миллионы фрагментов данных

MySQL

предисловие

В последнее время возникла необходимость разобрать файл заказа, а файл описания может достигать 10 миллионов штук данных.Каждая часть данных имеет около 20 полей.Каждое поле разделяется запятыми.Его нужно хранить в течении получаса как насколько это возможно.

идеи

1. Оцените размер файла

Поскольку существуют десятки миллионов файлов, а в каждой записи около 20 полей, можно приблизительно оценить размер всего файла заказа, и метод очень прост в использовании.FileWriterВставьте в файл 10 миллионов фрагментов данных, проверьте размер файла, после тестирования он составляет около 1,5 ГБ;

2. Как выполнить пакетную вставку

Из вышеизложенного видно, что файл относительно большой, и прочитать память за один раз точно не получится.Метод заключается в том, чтобы каждый раз перехватывать часть данных из файла текущего заказа, а затем выполнять пакетное Как пакетная вставка может использовать метод **insert(...)values(. ..),(...)**, эффективность этого метода достаточно высока после тестирования;

3. Целостность данных

При перехвате данных нужно обращать внимание на обеспечение целостности данных.Каждая запись это перевод строки в конце.Необходимо следить,чтобы каждый перехват был целым числом по этой идентификации,и такой ситуации быть не должно как половина данных;

4. Поддерживает ли база данных пакетные данные?

Поскольку пакетные данные должны быть вставлены, можно указать, поддерживает ли база данных запись большого объема данных, например, используемую здесь mysql, установивmax_allowed_packetОбеспечить количество данных, представляемых пакетами;

5. В случае ошибки посередине

Потому что это парсинг большого файла, если ошибка в середине, например, когда данные просто вставляются в 900w, происходит сбой подключения к базе данных.В этом случае невозможно повторно вставить его снова.Все, что нужно для записи положения каждого вставленного данных, и необходимо гарантировать и пакетно. Вставленные данные находятся в одной и той же транзакции, чтобы после восстановления вставку можно было продолжить с записанного положения.

выполнить

1. Подготовьте лист данных

Здесь необходимо подготовить две таблицы: таблица информации о состоянии и местонахождении заказа, таблица заказа;

CREATE TABLE `file_analysis` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `file_type` varchar(255) NOT NULL COMMENT '文件类型 01:类型1,02:类型2',
  `file_name` varchar(255) NOT NULL COMMENT '文件名称',
  `file_path` varchar(255) NOT NULL COMMENT '文件路径',
  `status` varchar(255) NOT NULL COMMENT '文件状态  0初始化;1成功;2失败:3处理中',
  `position` bigint(20) NOT NULL COMMENT '上一次处理完成的位置',
  `crt_time` datetime NOT NULL COMMENT '创建时间',
  `upd_time` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
CREATE TABLE `file_order` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `file_id` bigint(20) DEFAULT NULL,
  `field1` varchar(255) DEFAULT NULL,
  `field2` varchar(255) DEFAULT NULL,
  `field3` varchar(255) DEFAULT NULL,
  `field4` varchar(255) DEFAULT NULL,
  `field5` varchar(255) DEFAULT NULL,
  `field6` varchar(255) DEFAULT NULL,
  `field7` varchar(255) DEFAULT NULL,
  `field8` varchar(255) DEFAULT NULL,
  `field9` varchar(255) DEFAULT NULL,
  `field10` varchar(255) DEFAULT NULL,
  `field11` varchar(255) DEFAULT NULL,
  `field12` varchar(255) DEFAULT NULL,
  `field13` varchar(255) DEFAULT NULL,
  `field14` varchar(255) DEFAULT NULL,
  `field15` varchar(255) DEFAULT NULL,
  `field16` varchar(255) DEFAULT NULL,
  `field17` varchar(255) DEFAULT NULL,
  `field18` varchar(255) DEFAULT NULL,
  `crt_time` datetime NOT NULL COMMENT '创建时间',
  `upd_time` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10000024 DEFAULT CHARSET=utf8

2. Настройте размер пакета базы данных

mysql> show VARIABLES like '%max_allowed_packet%';
+--------------------------+------------+
| Variable_name            | Value      |
+--------------------------+------------+
| max_allowed_packet       | 1048576    |
| slave_max_allowed_packet | 1073741824 |
+--------------------------+------------+
2 rows in set

mysql> set global max_allowed_packet = 1024*1024*10;
Query OK, 0 rows affected

Установив max_allowed_packet, убедитесь, что база данных может получать размер пакета, вставленного в пакетах, иначе произойдет следующая ошибка:

Caused by: com.mysql.jdbc.PacketTooBigException: Packet for query is too large (4980577 > 1048576). You can change this value on the server by setting the max_allowed_packet' variable.
	at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:3915)
	at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2598)
	at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2778)
	at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2834)

3. Подготовьте тестовые данные

	public static void main(String[] args) throws IOException {
		FileWriter out = new FileWriter(new File("D://xxxxxxx//orders.txt"));
		for (int i = 0; i < 10000000; i++) {
			out.write(
					"vaule1,vaule2,vaule3,vaule4,vaule5,vaule6,vaule7,vaule8,vaule9,vaule10,vaule11,vaule12,vaule13,vaule14,vaule15,vaule16,vaule17,vaule18");
			out.write(System.getProperty("line.separator"));
		}
		out.close();
	}

Используйте FileWriter для обхода и вставки 1000 Вт фрагментов данных в файл. Эта скорость по-прежнему очень высока. Не забывайте добавлять после каждого фрагмента данных.новая строка (\n\r);

4. Целостность перехваченных данных

Помимо установки размера читаемого каждый раз файла, также необходимо задать параметр, чтобы каждый раз получать небольшую часть данных, и получать из этой небольшой части данныхновая строка (\n\r), если накопление не может быть накоплено напрямую до тех пор, пока оно не будет получено напрямую, установленный размер этого значения примерно равен размеру каждых данных, а некоторые реализации таковы:

ByteBuffer byteBuffer = ByteBuffer.allocate(buffSize); // 申请一个缓存区
long endPosition = batchFileSize + startPosition - buffSize;// 子文件结束位置

long startTime, endTime;
for (int i = 0; i < count; i++) {
	startTime = System.currentTimeMillis();
	if (i + 1 != count) {
		int read = inputChannel.read(byteBuffer, endPosition);// 读取数据
		readW: while (read != -1) {
			byteBuffer.flip();// 切换读模式
			byte[] array = byteBuffer.array();
			for (int j = 0; j < array.length; j++) {
				byte b = array[j];
				if (b == 10 || b == 13) { // 判断\n\r
					endPosition += j;
					break readW;
				}
			}
			endPosition += buffSize;
			byteBuffer.clear(); // 重置缓存块指针
			read = inputChannel.read(byteBuffer, endPosition);
		}
	} else {
		endPosition = fileSize; // 最后一个文件直接指向文件末尾
	}
    ...省略,更多可以查看Github完整代码...
}

Как показано в приведенном выше коде, открывается буфер размером около 200 байт в соответствии с размером данных каждой строки, а затем выполняется поиск путем обходановая строка (\n\r), найдя его, добавить текущую позицию к предыдущей конечной позиции, чтобы обеспечить целостность данных;

5. Пакетная вставка данных

Вставляйте данные пакетами с помощью **insert(...)values(...),(...)**, часть кода выглядит следующим образом:

// 保存订单和解析位置保证在一个事务中
		SqlSession session = sqlSessionFactory.openSession();
		try {
			long startTime = System.currentTimeMillis();
			FielAnalysisMapper fielAnalysisMapper = session.getMapper(FielAnalysisMapper.class);
			FileOrderMapper fileOrderMapper = session.getMapper(FileOrderMapper.class);
			fileOrderMapper.batchInsert(orderList);

			// 更新上次解析到的位置,同时指定更新时间
			fileAnalysis.setPosition(endPosition + 1);
			fileAnalysis.setStatus("3");
			fileAnalysis.setUpdTime(new Date());
			fielAnalysisMapper.updateFileAnalysis(fileAnalysis);
			session.commit();
			long endTime = System.currentTimeMillis();
			System.out.println("===插入数据花费:" + (endTime - startTime) + "ms===");
		} catch (Exception e) {
			session.rollback();
		} finally {
			session.close();
		}
        ...省略,更多可以查看Github完整代码...

Приведенный выше код одновременно сохраняет данные о пакетном заказе и информацию о местоположении для анализа файла в одной транзакции.

Суммировать

Выше показана часть кода. Чтобы увидеть полный код, вы можете просмотреть модуль batchInsert по адресу Github. Размер файла каждого перехваченного файла локально установлен на 2 М. После тестирования 1000 Вт фрагментов данных (около 1,5 ГБ ) вставляются в базу данных mysql, что занимает минут 20. Насчет минут, конечно, можно задать размер перехватываемого файла, соответственно изменится и затраченное время.

полный код

Github