最近做一個數(shù)據(jù)可視化項(xiàng)目時(shí),需要 Mock 大量的數(shù)據(jù)(千萬級)來進(jìn)行接口開發(fā)。本文將記錄探索實(shí)踐的全部過程。
1. 如何快速 Mock 大量數(shù)據(jù)
1.1 逐條插入數(shù)據(jù)
因?yàn)椴┲髦鳂I(yè)是搞前端開發(fā)的,對 MySQL 其實(shí)不是特別在行了。要 Mock 數(shù)據(jù),第一想法當(dāng)然是寫個程序或腳本來自動插入數(shù)據(jù)了。于是說干就干,很快一個基于 NodeJs 的 demo 就完成了。
建表 sql(為了演示方便,這里僅取4個字段,原測試 demo 有 21 個字段):
use test; CREATE TABLE test.data ( `id` INT NOT NULL, `nt` VARCHAR(100) NOT NULL, `imei` VARCHAR(100) NOT NULL, `model` VARCHAR(50) NOT NULL, PRIMARY KEY (id) )
逐條插入數(shù)據(jù)代碼:
let mysql = require('mysql');
const uuidv1 = require('uuid/v1');
let connection = mysql.createConnection({
host: 'localhost',
port: '6666',
user: 'root',
password: '123456',
database: 'test'
});
const close = () => connection.end();
function network() {
return ['"wifi"', '"g4"', '"g3"', '"g2"'][Math.floor(Math.random() * 4)]
}
function phone_model() {
return ['"NEX"','"x23"','"x21"','"x20"'][Math.floor(Math.random() * 4)]
}
function imei() {
return `"${uuidv1()}"`;
}
connection.connect();
let s = Date.now();
let i = 0;
function insert() {
connection.query(`insert into data(id, nt, imei, model) values (${i}, ${network()}, ${imei()}, ${phone_model()})`,
function (error, results, fields) {
if (error) throw error;
if (i + 1 < 100000) {
i++;
insert();
} else {
console.log('done:' + (Date.now() - s));
}
});
}
insert();
用 10 條數(shù)據(jù)測試了下,perfect!完美工作。清空表,數(shù)量加大到1萬條數(shù)據(jù)測試,勉強(qiáng) perfect ,這次插入1萬數(shù)據(jù)耗時(shí)48192ms,也就是48s左右,還能勉強(qiáng)接受。
繼續(xù)擴(kuò)量到10萬條數(shù)據(jù),這下就尷尬的很明顯了,生成10萬條數(shù)據(jù),一共耗時(shí)618983ms,618s,也就是10分鐘左右。大概心里估計(jì)了下,就算以線性遞增來算,那么100萬條數(shù)據(jù)大概就是100分鐘,1000萬數(shù)據(jù)大概是1000分鐘,也就是近17個小時(shí)。
很明顯,這效率太慢了,必須尋找效率更高的方式。
1.2 使用儲存過程批量插入數(shù)據(jù)
在網(wǎng)上搜索了一會兒,果然找到了一種新方式:使用 mysql 儲存過程來批量插入數(shù)據(jù)。所謂“儲存過程”,個人認(rèn)為就是批處理。
建表 sql,因?yàn)閿?shù)據(jù)量大,這里加上了分區(qū):
use test; CREATE TABLE test.data ( `id` INT NOT NULL, `nt` VARCHAR(100) NOT NULL, `imei` VARCHAR(100) NOT NULL, `model` VARCHAR(50) NOT NULL, PRIMARY KEY (id) ) ENGINE = MyISAM ROW_FORMAT = DEFAULT partition BY RANGE (id) ( partition p0 VALUES LESS THAN (10000000), partition p1 VALUES LESS THAN (20000000), partition p2 VALUES LESS THAN (30000000), partition p3 VALUES LESS THAN (40000000), partition p4 VALUES LESS THAN (50000000), partition p5 VALUES LESS THAN (60000000), partition p6 VALUES LESS THAN (70000000), partition p7 VALUES LESS THAN (80000000), partition p8 VALUES LESS THAN (90000000), Partition p9 VALUES LESS THAN MAXVALUE );
接著是創(chuàng)建 mysql 儲存過程,不過在編寫儲存過程代碼時(shí),遇到了一個問題,如何實(shí)現(xiàn)下面函數(shù)的功能,即隨機(jī)從 wigi, 4g, 3g, 2g 中返回一個網(wǎng)絡(luò)類型。
function network() {
return ['"wifi"', '"g4"', '"g3"', '"g2"'][Math.floor(Math.random() * 4)]
}
2. 如何在儲存過程中基于數(shù)組來生成隨機(jī)值
查了大量資料,發(fā)現(xiàn) mysql 儲存過程不支持?jǐn)?shù)組操作。一時(shí)間,似乎走到了死胡同。最后發(fā)現(xiàn)了這篇文章:MySQL函數(shù)和存儲過程生成電話號碼。作者生成電話號碼的思路給了我啟發(fā),于是我參照他的思路,實(shí)現(xiàn)了隨機(jī)生成網(wǎng)絡(luò)類型的功能。
---------------------------- -- 生成網(wǎng)絡(luò)類型的函數(shù) ---------------------------- DELIMITER $$ create function network() returns char(4) begin declare networks varchar(100) default "wifi4g 3g 2g "; -- 1,5,9,13 declare idx int; declare ret char(4); set idx = 1+floor(rand()*4)*4; set ret = trim(substring(networks,idx,4)); return ret; end $$ DELIMITER ;
大概思路就是:
- 找出數(shù)組中最長的字符串項(xiàng),比如 wifi,4g,3g,2g 中最長的項(xiàng)是 'wifi',長度為4
- 將數(shù)組所有項(xiàng)用空格填充,讓其與最長項(xiàng)長度一樣,即wifi4g 3g 2g
- 隨機(jī)生成固定的字符截取起始點(diǎn)。這里的隨機(jī),固定可能會不太好理解。看這個表達(dá)式1+floor(rand()*4)*4就清楚了,此表達(dá)式總是返回 1,5,9,13中某個值
- 截取字符串,同時(shí)去掉填充的空格,trim(substring(networks,idx,4)),就得到隨機(jī)值了
解決隨機(jī)生成值的問題后,儲存過程的代碼也就出來了:
----------------------------
-- 生成網(wǎng)絡(luò)類型的函數(shù)
----------------------------
DELIMITER $$
create function network() returns char(4)
begin
declare networks varchar(100) default "wifi4g 3g 2g "; -- 1,5,9,13
declare idx int;
declare ret char(4);
set idx = 1+floor(rand()*4)*4;
set ret = trim(substring(networks,idx,4));
return ret;
end $$
DELIMITER ;
----------------------------
-- 生成機(jī)型的函數(shù)
----------------------------
DELIMITER $$
create function phone_model() returns char(10)
begin
declare phone_types varchar(100) default "NEX x23 x21 x20 x9 x7 x6 x5 Z1 Z2 Z3 Y97 Y91 Y85 Y83 Y81 Y79 ";
declare idx int;
declare ret char(10);
set idx = 1+floor(rand()*17)*4;
set ret = trim(substring(phone_types,idx,4));
return ret;
end $$
DELIMITER ;
----------------------------
-- 生成IMEI的函數(shù)
----------------------------
DELIMITER $$
create function randchar() returns char(5)
begin
declare ret char(5);
set ret = substring("ABCDEFGHIJKLMNOPQRSTUVWXYZ",floor(1+26*rand()),1);
return ret;
end $$
DELIMITER ;
DELIMITER $$
create function imei() returns char(50)
begin
declare ret char(50) default "";
declare imeiLen int default 11;
DECLARE idx INT default 0;
WHILE idx < imeiLen DO
SET idx = idx + 1;
SET ret = CONCAT(ret, randchar());
END WHILE;
return ret;
end $$
DELIMITER ;
----------------------------
-- 創(chuàng)建儲存過程
----------------------------
use test;
DROP PROCEDURE IF EXISTS test.BatchInsertCustomer;
delimiter //
CREATE PROCEDURE BatchInsertCustomer(IN start INT,IN loop_time INT)
BEGIN
DECLARE Var INT;
DECLARE ID INT;
SET Var = 0;
SET ID= start;
WHILE Var < loop_time DO
insert into data(`id`, `nt`, `imei`, `model`)
values (ID, network(), imei(), phone_model());
SET Var = Var + 1;
SET ID = ID + 1;
END WHILE;
END;
//
delimiter ;
調(diào)用儲存過程:
-- 調(diào)用 ALTER TABLE test DISABLE KEYS; CALL BatchInsertCustomer(1, 10); ALTER TABLE test ENABLE KEYS;
在測試時(shí),使用儲存過程生成1000萬數(shù)據(jù)大概是140分鐘,不到2個半小時(shí)。相比逐條插入的17個小時(shí),快了8,9倍,效率提升不少。
3. 小結(jié)
使用 mysql 儲存過程可以快速地生成 Mock 數(shù)據(jù)。同時(shí)本文還提供了一種“如何在儲存過程中基于數(shù)組來生成隨機(jī)值“的思路,希望對大家有些幫助。
3.1 參考
- MySQL批量插入大量數(shù)據(jù)方法
- MySQL函數(shù)和存儲過程生成電話號碼






