最近做一個(gè)數(shù)據(jù)可視化項(xiàng)目時(shí),需要 Mock 大量的數(shù)據(jù)(千萬(wàn)級(jí))來(lái)進(jìn)行接口開(kāi)發(fā)。本文將記錄探索實(shí)踐的全部過(guò)程。
1. 如何快速 Mock 大量數(shù)據(jù)
1.1 逐條插入數(shù)據(jù)
因?yàn)椴┲髦鳂I(yè)是搞前端開(kāi)發(fā)的,對(duì) MySQL 其實(shí)不是特別在行了。要 Mock 數(shù)據(jù),第一想法當(dāng)然是寫(xiě)個(gè)程序或腳本來(lái)自動(dòng)插入數(shù)據(jù)了。于是說(shuō)干就干,很快一個(gè)基于 NodeJs 的 demo 就完成了。
建表 sql(為了演示方便,這里僅取4個(gè)字段,原測(cè)試 demo 有 21 個(gè)字段):
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ù)測(cè)試了下,perfect!完美工作。清空表,數(shù)量加大到1萬(wàn)條數(shù)據(jù)測(cè)試,勉強(qiáng) perfect ,這次插入1萬(wàn)數(shù)據(jù)耗時(shí)48192ms,也就是48s左右,還能勉強(qiáng)接受。
繼續(xù)擴(kuò)量到10萬(wàn)條數(shù)據(jù),這下就尷尬的很明顯了,生成10萬(wàn)條數(shù)據(jù),一共耗時(shí)618983ms,618s,也就是10分鐘左右。大概心里估計(jì)了下,就算以線(xiàn)性遞增來(lái)算,那么100萬(wàn)條數(shù)據(jù)大概就是100分鐘,1000萬(wàn)數(shù)據(jù)大概是1000分鐘,也就是近17個(gè)小時(shí)。
很明顯,這效率太慢了,必須尋找效率更高的方式。
1.2 使用儲(chǔ)存過(guò)程批量插入數(shù)據(jù)
在網(wǎng)上搜索了一會(huì)兒,果然找到了一種新方式:使用 mysql 儲(chǔ)存過(guò)程來(lái)批量插入數(shù)據(jù)。所謂“儲(chǔ)存過(guò)程”,個(gè)人認(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 儲(chǔ)存過(guò)程,不過(guò)在編寫(xiě)儲(chǔ)存過(guò)程代碼時(shí),遇到了一個(gè)問(wèn)題,如何實(shí)現(xiàn)下面函數(shù)的功能,即隨機(jī)從 wigi, 4g, 3g, 2g 中返回一個(gè)網(wǎng)絡(luò)類(lèi)型。
function network() { return ['"wifi"', '"g4"', '"g3"', '"g2"'][Math.floor(Math.random() * 4)] }
2. 如何在儲(chǔ)存過(guò)程中基于數(shù)組來(lái)生成隨機(jī)值
查了大量資料,發(fā)現(xiàn) mysql 儲(chǔ)存過(guò)程不支持?jǐn)?shù)組操作。一時(shí)間,似乎走到了死胡同。最后發(fā)現(xiàn)了這篇文章:MySQL函數(shù)和存儲(chǔ)過(guò)程生成電話(huà)號(hào)碼。作者生成電話(huà)號(hào)碼的思路給了我啟發(fā),于是我參照他的思路,實(shí)現(xiàn)了隨機(jī)生成網(wǎng)絡(luò)類(lèi)型的功能。
---------------------------- -- 生成網(wǎng)絡(luò)類(lèi)型的函數(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ù)組中最長(zhǎng)的字符串項(xiàng),比如 wifi,4g,3g,2g 中最長(zhǎng)的項(xiàng)是 'wifi',長(zhǎng)度為4
- 將數(shù)組所有項(xiàng)用空格填充,讓其與最長(zhǎng)項(xiàng)長(zhǎng)度一樣,即wifi4g 3g 2g
- 隨機(jī)生成固定的字符截取起始點(diǎn)。這里的隨機(jī),固定可能會(huì)不太好理解。看這個(gè)表達(dá)式1+floor(rand()*4)*4就清楚了,此表達(dá)式總是返回 1,5,9,13中某個(gè)值
- 截取字符串,同時(shí)去掉填充的空格,trim(substring(networks,idx,4)),就得到隨機(jī)值了
解決隨機(jī)生成值的問(wèn)題后,儲(chǔ)存過(guò)程的代碼也就出來(lái)了:
---------------------------- -- 生成網(wǎng)絡(luò)類(lèi)型的函數(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)建儲(chǔ)存過(guò)程 ---------------------------- 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)用儲(chǔ)存過(guò)程:
-- 調(diào)用 ALTER TABLE test DISABLE KEYS; CALL BatchInsertCustomer(1, 10); ALTER TABLE test ENABLE KEYS;
在測(cè)試時(shí),使用儲(chǔ)存過(guò)程生成1000萬(wàn)數(shù)據(jù)大概是140分鐘,不到2個(gè)半小時(shí)。相比逐條插入的17個(gè)小時(shí),快了8,9倍,效率提升不少。
3. 小結(jié)
使用 mysql 儲(chǔ)存過(guò)程可以快速地生成 Mock 數(shù)據(jù)。同時(shí)本文還提供了一種“如何在儲(chǔ)存過(guò)程中基于數(shù)組來(lái)生成隨機(jī)值“的思路,希望對(duì)大家有些幫助。
3.1 參考
- MySQL批量插入大量數(shù)據(jù)方法
- MySQL函數(shù)和存儲(chǔ)過(guò)程生成電話(huà)號(hào)碼