Y++ 社区

Y++ 百万行进 MySQL:表格秒开 + 多值 INSERT 分批入库

源码 扩展类库示例 浏览 1 管理员

Y++MySQL数据库表格大数据导入

Y++ 百万行进 MySQL:表格秒开 + 多值 INSERT 分批入库

登录后收藏

约 3359 字 · 9 分钟阅读

最近做了个小示例,专门练「大批量怎么快进库」。

思路就三步:

  1. 先准备好 data\rows_100w.txt(100 万行:编号,姓名,年龄)
  2. 点「导入到表格」——用表格的 导入文件,虚表加载,百万行秒级能刷出来
  3. 点「导入到数据库」——后台事务里每批拼一条多值 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, &进度滴答)
}

函数 按钮清空日志.单击 () {
    日志框.文本 = ""
}

 

 

相关官方文档

回复 (0)

还没有回复,来抢沙发吧。