Y++ 百万行进 MySQL:表格秒开 + 多值 INSERT 分批入库
Y++ 百万行进 MySQL:表格秒开 + 多值 INSERT 分批入库
约 3359 字 · 9 分钟阅读
最近做了个小示例,专门练「大批量怎么快进库」。
思路就三步:
- 先准备好
data\rows_100w.txt(100 万行:编号,姓名,年龄) - 点「导入到表格」——用表格的
导入文件,虚表加载,百万行秒级能刷出来 - 点「导入到数据库」——后台事务里每批拼一条多值 INSERT:
INSERT INTO demo_orders (编号,姓名,年龄) VALUES
(0,'用户0',18),(1,'用户1',19),... -- 每批约 2000 行
别再一行一条 插入() 了,往返次数太夸张。示例里顺带关了 unique_checks / foreign_key_checks,提交后再打开。


Y++完整代码:
MySQL类 g_db
定时器类 g_钟
整数 g_钟Id = 0
文本 g_数据文件 = ""
文本 g_表名 = "demo_orders"
整数 g_每批 = 2000
整数 g_行数 = 0
文本 g_主机 = ""
整数 g_端口 = 3306
文本 g_用户 = ""
文本 g_密码 = ""
文本 g_库名 = ""
逻辑 g_忙碌 = 假
整数 g_进度 = 0
整数 g_上次进度 = -1
文本 g_后台消息 = ""
逻辑 g_入完 = 假
逻辑 g_失败 = 假
整数 g_开始戳 = 0
函数 记日志 (文本 s) {
日志框.加入文本(s + "\r\n")
输出(s)
}
函数 置按钮可用 (逻辑 可用) {
按钮导入表格.启用 = 可用
按钮入库.启用 = 可用
}
函数 停表 () {
如果 (g_钟Id != 0) {
g_钟.停止(g_钟Id)
g_钟Id = 0
}
}
函数 准备表格 () {
表格1.置列数(3)
表格1.置表头(0, "编号")
表格1.置表头(1, "姓名")
表格1.置表头(2, "年龄")
表格1.置列宽(0, 100)
表格1.置列宽(1, 220)
表格1.置列宽(2, 80)
}
函数 默认数据文件 () 文本 {
返回(合并路径(取程序目录(), "data\\rows_100w.txt"))
}
函数 后台入库 () {
g_后台消息 = "连接数据库…"
如果 (g_db.是否打开()) {
g_db.关闭()
}
g_db.MySQL版本(2)
如果 (g_db.打开(g_主机, g_端口, g_用户, g_密码, "", "utf8mb4") == 假) {
g_后台消息 = "连接失败:" + g_db.取错误()
g_失败 = 真
g_入完 = 真
返回()
}
如果 (g_db.执行SQL("CREATE DATABASE IF NOT EXISTS `" + g_库名 + "` DEFAULT CHARSET utf8mb4") == 假) {
g_后台消息 = "建库失败:" + g_db.取错误()
g_失败 = 真
g_入完 = 真
返回()
}
如果 (g_db.选择库(g_库名) == 假) {
g_后台消息 = "选库失败:" + g_db.取错误()
g_失败 = 真
g_入完 = 真
返回()
}
g_db.执行SQL("DROP TABLE IF EXISTS `" + g_表名 + "`")
文本 ddl = "CREATE TABLE `" + g_表名 + "` ("
+ "`编号` INT NOT NULL,"
+ "`姓名` VARCHAR(64) NOT NULL,"
+ "`年龄` INT NOT NULL,"
+ "PRIMARY KEY (`编号`)"
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
如果 (g_db.执行SQL(ddl) == 假) {
g_后台消息 = "建表失败:" + g_db.取错误()
g_失败 = 真
g_入完 = 真
返回()
}
g_后台消息 = "读数据文件…"
文本 行[] = 读文件行(g_数据文件, #编码_UTF8, 真, 0)
整数 行数 = 数组长度(行)
如果 (行数 < 1) {
g_后台消息 = "数据文件为空或读失败"
g_失败 = 真
g_入完 = 真
返回()
}
g_行数 = 行数
g_db.执行SQL("SET unique_checks=0")
g_db.执行SQL("SET foreign_key_checks=0")
如果 (g_db.开始事务() == 假) {
g_后台消息 = "开始事务失败:" + g_db.取错误()
g_失败 = 真
g_入完 = 真
返回()
}
g_后台消息 = "多值 INSERT 入库中…"
g_进度 = 0
整数 i = 0
整数 批号 = 0
当 (i < 行数) {
整数 本批末 = i + g_每批
如果 (本批末 > 行数) {
本批末 = 行数
}
文本 sql = "INSERT INTO `" + g_表名 + "` (`编号`,`姓名`,`年龄`) VALUES "
整数 r = i
当 (r < 本批末) {
文本 段[] = 文本分割(行[r], ",")
如果 (数组长度(段) < 3) {
g_后台消息 = "第 " + 到文本(r + 1) + " 行格式不对"
g_失败 = 真
g_db.回滚()
g_入完 = 真
返回()
}
如果 (r > i) {
sql = sql + ","
}
sql = sql + "(" + 段[0] + ",'" + g_db.转义文本(段[1]) + "'," + 段[2] + ")"
r = r + 1
}
如果 (g_db.执行SQL(sql) == 假) {
g_后台消息 = "第 " + 到文本(批号 + 1) + " 批失败:" + g_db.取错误()
g_失败 = 真
g_db.回滚()
g_入完 = 真
返回()
}
批号 = 批号 + 1
i = 本批末
g_进度 = i
}
如果 (g_db.提交() == 假) {
g_后台消息 = "提交失败:" + g_db.取错误()
g_失败 = 真
g_db.回滚()
g_入完 = 真
返回()
}
g_db.执行SQL("SET unique_checks=1")
g_db.执行SQL("SET foreign_key_checks=1")
g_进度 = 行数
g_后台消息 = "入库完成,库内行数=" + 到文本(g_db.取行数(g_表名)) + ",共 " + 到文本(批号) + " 批"
g_入完 = 真
}
函数 进度滴答 () {
如果 (g_进度 != g_上次进度) {
g_上次进度 = g_进度
如果 (g_行数 > 0) {
记日志("入库进度 " + 到文本(g_进度) + " / " + 到文本(g_行数))
}
}
如果 (g_入完 == 假) {
返回()
}
g_入完 = 假
整数 ms = 取时间戳(, 真) - g_开始戳
如果 (g_失败) {
记日志(g_后台消息)
} 否则 {
记日志(g_后台消息 + ",总用时 " + 到文本(ms) + " ms")
}
停表()
g_忙碌 = 假
置按钮可用(真)
}
函数 主窗口.创建完毕 () {
准备表格()
编辑主机.文本 = "127.0.0.1"
编辑端口.文本 = "3306"
编辑用户.文本 = "root"
编辑密码.文本 = "root"
编辑库名.文本 = "ypp_bulk"
g_数据文件 = 默认数据文件()
记日志("就绪:先「导入到表格」,再「导入到数据库」。")
记日志("数据文件:" + g_数据文件)
如果 (文件存在(g_数据文件) == 假) {
记日志("未找到数据文件。请在工程目录执行:powershell -File _gen_rows.ps1")
}
}
函数 按钮导入表格.单击 () {
如果 (g_忙碌) {
记日志("正在入库,请稍候")
返回()
}
g_数据文件 = 默认数据文件()
如果 (文件存在(g_数据文件) == 假) {
记日志("文件不存在:" + g_数据文件)
记日志("请先运行:powershell -File _gen_rows.ps1")
返回()
}
准备表格()
表格1.置行数(0)
记日志("导入表格:" + g_数据文件)
整数 t0 = 取时间戳(, 真)
表格1.导入文件(g_数据文件, ",")
整数 ms = 取时间戳(, 真) - t0
记日志("表格行数=" + 到文本(表格1.取行数()) + ",用时 " + 到文本(ms) + " ms")
}
函数 按钮入库.单击 () {
如果 (g_忙碌) {
记日志("正在忙,请稍候")
返回()
}
g_数据文件 = 默认数据文件()
如果 (文件存在(g_数据文件) == 假) {
记日志("找不到数据文件,请先 _gen_rows.ps1 生成")
返回()
}
如果 (表格1.取行数() < 1) {
记日志("建议先点「导入到表格」看一下数据;也可直接入库(按同一文件写入)")
}
g_主机 = 编辑主机.文本
g_端口 = 到整数(编辑端口.文本)
g_用户 = 编辑用户.文本
g_密码 = 编辑密码.文本
g_库名 = 编辑库名.文本
g_忙碌 = 真
g_失败 = 假
g_入完 = 假
g_进度 = 0
g_上次进度 = -1
g_行数 = 0
g_开始戳 = 取时间戳(, 真)
置按钮可用(假)
记日志("开始后台入库(每批 " + 到文本(g_每批) + " 行多值 INSERT)…")
启动线程(&后台入库)
g_钟Id = g_钟.启动(300, &进度滴答)
}
函数 按钮清空日志.单击 () {
日志框.文本 = ""
}
还没有回复,来抢沙发吧。