前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# 项目建表初始化

30 秒速记

  • 做法:SQL 脚本放 sql/ 目录 → Node 读出来 → 按 ; 拆成单条 → 逐条 await query 执行
  • 建表用 CREATE TABLE IF NOT EXISTS,重复跑不报错;但改表结构它管不了
  • 按 ; 拆分是简化做法,字符串或存储过程里带分号就会拆错
  • 执行完要 pool.end(),不然连接池挂着,进程不退出
  • 正式项目用迁移工具:Knex / Sequelize migration、Prisma Migrate、Flyway

建表初始化就是把建表 SQL 存成文件放进仓库,写个脚本一键执行,新人拉代码、换环境都能一条命令把库建好。 流程很简单:遍历 sql/ 目录拿到所有 .sql 文件,读出内容,按分号拆成单条语句,依次执行并打印成败。它适合学习和小项目,但有个明显的局限:IF NOT EXISTS 只管「没有就建」,表已经存在又要加字段,它就无能为力了。真实项目表结构会不断演进,我会用迁移工具,每次变更一个版本号文件,记录执行到了哪一版。

理解「项目建表初始化」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

项目建表初始化这件事,核心是让「数据库结构」和代码一起进版本管理。下面是一个比较稳的最小实现,可以直接拿来改:

// scripts/init-db.js
const fs = require('fs')
const path = require('path')
const mysql = require('mysql2/promise')

async function main() {
  const conn = await mysql.createConnection({
    host: '127.0.0.1',
    user: 'root',
    password: process.env.DB_PASSWORD,
    database: 'koa_demo',
    multipleStatements: true, // 允许一次执行整个文件里的多条语句
  })

  const dir = path.join(__dirname, '../sql')
  const files = fs.readdirSync(dir).filter(f => f.endsWith('.sql')).sort() // 001_xxx.sql 保证顺序

  try {
    for (const file of files) {
      const sql = fs.readFileSync(path.join(dir, file), 'utf8')
      await conn.query(sql)
      console.log(`[SUCCESS] ${file}`)
    }
  } catch (err) {
    console.error(`[ERROR] ${err.code}: ${err.message}`)
    process.exitCode = 1 // 让 CI 能感知失败
  } finally {
    await conn.end() // 关掉连接,进程自然退出
  }
}

main()
@前端进阶之旅: 代码已经复制到剪贴板

这里有几处和「按分号拆分逐条执行」不同的考虑:

  • multipleStatements: true 让驱动整份文件一起发,不用自己拆,字符串里带分号也不会出错。代价是这个选项会放大注入风险,所以只在初始化脚本里开,业务连接池别开。
  • 文件按名字排序,用序号前缀控制执行顺序,有外键依赖时很重要。
  • 失败时设置 process.exitCode = 1,CI 或部署脚本才能发现初始化失败了。

当表结构开始频繁变化,就该换成迁移工具了:每次变更写一个新的迁移文件,工具在库里记录执行到哪一版,只跑新的,必要时还能回滚。

面试官追问

追问 1表已经存在,现在要给 user 表加一个 avatar 字段,再跑这个初始化脚本有用吗?
参考回答

没用,CREATE TABLE IF NOT EXISTS 发现表存在就直接跳过了。要写 ALTER TABLE user ADD COLUMN avatar VARCHAR(255),而且要记录这条执行过没有,这正是迁移工具做的事。

追问 2脚本跑完打印了「执行结束」,但命令行一直不退出,为什么?
参考回答

连接池里还有活着的连接,事件循环不会结束。脚本最后加一句 pool.end() 就能自己退出,不用让人按 Ctrl + C。

追问 3user.sql 里有 INSERT 初始数据,跑两次会怎样?
参考回答

建表那句因为 IF NOT EXISTS 没事,INSERT 会再插一遍,数据重复了。种子数据要用 INSERT IGNORE 配唯一索引,或者和建表脚本分开、只在空库时执行。

追问 4这种脚本和 Prisma Migrate 比,差在哪?
参考回答

缺版本记录。迁移工具会在库里建一张表记录哪些迁移已执行,每次只跑新的,还能回滚;这个脚本每次全量重跑,只能靠 IF NOT EXISTS 兜底。

追问 5两张表有外键关系,脚本执行顺序乱了会怎样?
参考回答

先建子表会因为引用的父表不存在而失败。readdirSync 的顺序不保证,文件名加序号前缀比如 001_user.sql,读出来后 sort() 一下再执行。

# 建表初始化

# 前言

30 秒速记

  • 痛点:项目开发期表结构经常变,手动在客户端里建表容易漏、各环境不一致
  • 解法:建表 SQL 放进仓库的 sql/ 目录,写个脚本一键重建
  • 本质是「数据库结构即代码」,和代码一起 review、一起回滚
  • 局限:只能全量重建,改表结构、保留旧数据要靠迁移工具

为什么要封装建表初始化?因为开发阶段表结构天天在改,靠人在图形化客户端里点很容易漏,把 SQL 放进仓库就能保证每个人、每个环境建出来的表都一样。 新同事拉下代码,执行一条 node index.js 就有了完整的库,不用找人要建表语句。这种「全量重建」的思路在开发阶段很好用,因为数据可以随便丢;一旦上线有了真实数据,就不能删了重建,要换成增量迁移。

通常初始化数据库要建立很多表,特别在项目开发的时候表的格式可能会有些变动,这时候就需要封装对数据库建表初始化的方法,保留项目的sql脚本文件,然后每次需要重新建表,则执行建表初始化程序就行

面试官追问

追问 1开发环境随便重建,那上线之后表结构要改怎么办?
参考回答

上线后不能删表重建,要写增量的 ALTER 语句,并且记录执行过哪些。这就是迁移工具的工作,每次变更一个文件,按顺序执行,库里有一张表记着当前版本。

追问 2同事在客户端里手动给表加了个索引,没写进 SQL 文件,会有什么后果?
参考回答

他本地快了,别人和线上都没有这个索引,问题很难复现。所有结构变更都必须走仓库里的脚本,客户端只用来查数据。

追问 3测试环境每次跑 CI 都要一个干净的库,这套脚本能用上吗?
参考回答

正好合适。CI 里起一个 MySQL 容器,跑一遍初始化脚本建表、插种子数据,再跑测试,每次都是干净且一致的。

追问 4建表 SQL 放仓库里,有没有什么不能放进去的?
参考回答

真实用户数据、生产账号密码不能放。种子数据只放测试用的假数据,连接配置走环境变量。

# 快速开始

30 秒速记

  • 准备:本地 MySQL 跑起来、建好 koa_demo 库,db.js 里的账号密码改成自己的
  • 运行:npm install → node index.js,控制台逐条打印 SQL 执行结果
  • 验证:客户端里看 user、data 两张表建好了,user 里有 3 条初始数据
  • 脚本不会自动退出,要手动 Ctrl + C;加一句 pool.end() 就能自己结束

跑这个 demo 前要先把数据库准备好,脚本只负责建表,不负责建库。 也就是说 koa_demo 这个库得先 CREATE DATABASE 出来,db.js 里的 host、user、password 改成你本地的,不然一上来就是连接报错。跑完之后别只看控制台打印的成功,打开客户端确认表结构和数据确实写进去了。如果用的是 MySQL 8 加老的 mysql 包,大概率会遇到认证报错,换 mysql2 最省事。

demo源码

https://github.com/poetries/daily-code-practice/tree/master/node/koa/koa2-demo/mysql/

面试官追问

追问 1一跑就报 ER_BAD_DB_ERROR: Unknown database 'koa_demo',怎么回事?
参考回答

库还没建。脚本连接时指定了 database,库不存在就直接失败。先 CREATE DATABASE koa_demo DEFAULT CHARSET utf8mb4 再跑。

追问 2控制台全是 SUCCESS,怎么确认数据真的写进去了?
参考回答

到客户端里执行 SHOW TABLES 和 SELECT COUNT(*) FROM user,看到两张表和 3 条数据才算真的成功。日志只是程序自己说的。

追问 3想让脚本跑完自己退出,怎么改?
参考回答

所有 SQL 执行完后调用 pool.end(),连接都关掉了,事件循环没事可做,进程就会退出。

追问 4同一个 demo 换台电脑跑,报认证插件不支持,为什么前一台没事?
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

项目建表初始化|Nodejs专题系列

# 项目建表初始化

30 秒速记

  • 做法:SQL 脚本放 sql/ 目录 → Node 读出来 → 按 ; 拆成单条 → 逐条 await query 执行
  • 建表用 CREATE TABLE IF NOT EXISTS,重复跑不报错;但改表结构它管不了
  • 按 ; 拆分是简化做法,字符串或存储过程里带分号就会拆错
  • 执行完要 pool.end(),不然连接池挂着,进程不退出
  • 正式项目用迁移工具:Knex / Sequelize migration、Prisma Migrate、Flyway

建表初始化就是把建表 SQL 存成文件放进仓库,写个脚本一键执行,新人拉代码、换环境都能一条命令把库建好。 流程很简单:遍历 sql/ 目录拿到所有 .sql 文件,读出内容,按分号拆成单条语句,依次执行并打印成败。它适合学习和小项目,但有个明显的局限:IF NOT EXISTS 只管「没有就建」,表已经存在又要加字段,它就无能为力了。真实项目表结构会不断演进,我会用迁移工具,每次变更一个版本号文件,记录执行到了哪一版。

理解「项目建表初始化」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

项目建表初始化这件事,核心是让「数据库结构」和代码一起进版本管理。下面是一个比较稳的最小实现,可以直接拿来改:

// scripts/init-db.js
const fs = require('fs')
const path = require('path')
const mysql = require('mysql2/promise')

async function main() {
  const conn = await mysql.createConnection({
    host: '127.0.0.1',
    user: 'root',
    password: process.env.DB_PASSWORD,
    database: 'koa_demo',
    multipleStatements: true, // 允许一次执行整个文件里的多条语句
  })

  const dir = path.join(__dirname, '../sql')
  const files = fs.readdirSync(dir).filter(f => f.endsWith('.sql')).sort() // 001_xxx.sql 保证顺序

  try {
    for (const file of files) {
      const sql = fs.readFileSync(path.join(dir, file), 'utf8')
      await conn.query(sql)
      console.log(`[SUCCESS] ${file}`)
    }
  } catch (err) {
    console.error(`[ERROR] ${err.code}: ${err.message}`)
    process.exitCode = 1 // 让 CI 能感知失败
  } finally {
    await conn.end() // 关掉连接,进程自然退出
  }
}

main()
@前端进阶之旅: 代码已经复制到剪贴板

这里有几处和「按分号拆分逐条执行」不同的考虑:

  • multipleStatements: true 让驱动整份文件一起发,不用自己拆,字符串里带分号也不会出错。代价是这个选项会放大注入风险,所以只在初始化脚本里开,业务连接池别开。
  • 文件按名字排序,用序号前缀控制执行顺序,有外键依赖时很重要。
  • 失败时设置 process.exitCode = 1,CI 或部署脚本才能发现初始化失败了。

当表结构开始频繁变化,就该换成迁移工具了:每次变更写一个新的迁移文件,工具在库里记录执行到哪一版,只跑新的,必要时还能回滚。

面试官追问

追问 1表已经存在,现在要给 user 表加一个 avatar 字段,再跑这个初始化脚本有用吗?
参考回答

没用,CREATE TABLE IF NOT EXISTS 发现表存在就直接跳过了。要写 ALTER TABLE user ADD COLUMN avatar VARCHAR(255),而且要记录这条执行过没有,这正是迁移工具做的事。

追问 2脚本跑完打印了「执行结束」,但命令行一直不退出,为什么?
参考回答

连接池里还有活着的连接,事件循环不会结束。脚本最后加一句 pool.end() 就能自己退出,不用让人按 Ctrl + C。

追问 3user.sql 里有 INSERT 初始数据,跑两次会怎样?
参考回答

建表那句因为 IF NOT EXISTS 没事,INSERT 会再插一遍,数据重复了。种子数据要用 INSERT IGNORE 配唯一索引,或者和建表脚本分开、只在空库时执行。

追问 4这种脚本和 Prisma Migrate 比,差在哪?
参考回答

缺版本记录。迁移工具会在库里建一张表记录哪些迁移已执行,每次只跑新的,还能回滚;这个脚本每次全量重跑,只能靠 IF NOT EXISTS 兜底。

追问 5两张表有外键关系,脚本执行顺序乱了会怎样?
参考回答

先建子表会因为引用的父表不存在而失败。readdirSync 的顺序不保证,文件名加序号前缀比如 001_user.sql,读出来后 sort() 一下再执行。

# 建表初始化

# 前言

30 秒速记

  • 痛点:项目开发期表结构经常变,手动在客户端里建表容易漏、各环境不一致
  • 解法:建表 SQL 放进仓库的 sql/ 目录,写个脚本一键重建
  • 本质是「数据库结构即代码」,和代码一起 review、一起回滚
  • 局限:只能全量重建,改表结构、保留旧数据要靠迁移工具

为什么要封装建表初始化?因为开发阶段表结构天天在改,靠人在图形化客户端里点很容易漏,把 SQL 放进仓库就能保证每个人、每个环境建出来的表都一样。 新同事拉下代码,执行一条 node index.js 就有了完整的库,不用找人要建表语句。这种「全量重建」的思路在开发阶段很好用,因为数据可以随便丢;一旦上线有了真实数据,就不能删了重建,要换成增量迁移。

通常初始化数据库要建立很多表,特别在项目开发的时候表的格式可能会有些变动,这时候就需要封装对数据库建表初始化的方法,保留项目的sql脚本文件,然后每次需要重新建表,则执行建表初始化程序就行

面试官追问

追问 1开发环境随便重建,那上线之后表结构要改怎么办?
参考回答

上线后不能删表重建,要写增量的 ALTER 语句,并且记录执行过哪些。这就是迁移工具的工作,每次变更一个文件,按顺序执行,库里有一张表记着当前版本。

追问 2同事在客户端里手动给表加了个索引,没写进 SQL 文件,会有什么后果?
参考回答

他本地快了,别人和线上都没有这个索引,问题很难复现。所有结构变更都必须走仓库里的脚本,客户端只用来查数据。

追问 3测试环境每次跑 CI 都要一个干净的库,这套脚本能用上吗?
参考回答

正好合适。CI 里起一个 MySQL 容器,跑一遍初始化脚本建表、插种子数据,再跑测试,每次都是干净且一致的。

追问 4建表 SQL 放仓库里,有没有什么不能放进去的?
参考回答

真实用户数据、生产账号密码不能放。种子数据只放测试用的假数据,连接配置走环境变量。

# 快速开始

30 秒速记

  • 准备:本地 MySQL 跑起来、建好 koa_demo 库,db.js 里的账号密码改成自己的
  • 运行:npm install → node index.js,控制台逐条打印 SQL 执行结果
  • 验证:客户端里看 user、data 两张表建好了,user 里有 3 条初始数据
  • 脚本不会自动退出,要手动 Ctrl + C;加一句 pool.end() 就能自己结束

跑这个 demo 前要先把数据库准备好,脚本只负责建表,不负责建库。 也就是说 koa_demo 这个库得先 CREATE DATABASE 出来,db.js 里的 host、user、password 改成你本地的,不然一上来就是连接报错。跑完之后别只看控制台打印的成功,打开客户端确认表结构和数据确实写进去了。如果用的是 MySQL 8 加老的 mysql 包,大概率会遇到认证报错,换 mysql2 最省事。

demo源码

https://github.com/poetries/daily-code-practice/tree/master/node/koa/koa2-demo/mysql/

面试官追问

追问 1一跑就报 ER_BAD_DB_ERROR: Unknown database 'koa_demo',怎么回事?
参考回答

库还没建。脚本连接时指定了 database,库不存在就直接失败。先 CREATE DATABASE koa_demo DEFAULT CHARSET utf8mb4 再跑。

追问 2控制台全是 SUCCESS,怎么确认数据真的写进去了?
参考回答

到客户端里执行 SHOW TABLES 和 SELECT COUNT(*) FROM user,看到两张表和 3 条数据才算真的成功。日志只是程序自己说的。

追问 3想让脚本跑完自己退出,怎么改?
参考回答

所有 SQL 执行完后调用 pool.end(),连接都关掉了,事件循环没事可做,进程就会退出。

追问 4同一个 demo 换台电脑跑,报认证插件不支持,为什么前一台没事?
参考回答

前一台可能是 MySQL 5.7,默认认证是 mysql_native_password;新电脑装的 MySQL 8 默认 caching_sha2_password,老的 mysql 包不支持。换 mysql2 就好。

# 源码目录

30 秒速记

  • index.js:入口,读所有脚本、逐条执行、打印日志
  • sql/:建表和初始数据脚本,一张表一个文件
  • util/db.js:连接池 + Promise 版 query
  • util/walk-file.js → get-sql-map.js → get-sql-content-map.js:找目录 → 筛 .sql 文件 → 读出内容
  • 工具函数拆得很细,学习时好理解;实际项目一个 readdirSync + filter 就够

目录分三块:sql/ 放脚本,util/ 放工具,index.js 负责把它们串起来。 util 里的三个文件其实是一条流水线:walk-file.js 遍历目录按后缀筛文件,get-sql-map.js 算出 sql/ 目录的绝对路径并调用它,get-sql-content-map.js 再把每个文件内容读出来,得到「文件名 → SQL 内容」的对象。db.js 和这条流水线互不依赖,只负责执行。这样拆的好处是每个文件职责单一,换数据库或者换脚本来源都只改一处。

├── index.js # 程序入口文件
├── node_modules/
├── package.json
├── sql   # sql脚本文件目录
│   ├── data.sql
│   └── user.sql
└── util    # 工具代码
    ├── db.js # 封装的mysql模块方法
    ├── get-sql-content-map.js # 获取sql脚本文件内容
    ├── get-sql-map.js # 获取所有sql脚本文件
    └── walk-file.js # 遍历sql脚本文件
@前端进阶之旅: 代码已经复制到剪贴板

← 8.2 async await封装使用mysql9.1 原生koa2实现JSONP →

fe