MySQL Studio
在 VS Code 侧边栏里浏览 MySQL 8 数据源,查看和修改表数据、表结构,在命令窗口里执行 SQL,做表结构迁移、表数据同步和表导出,看实例的监控、会话和锁。
侧边栏顶部四个页签:
| 页签 |
做什么 |
| 数据源 |
添加 / 编辑 / 测试 MySQL 连接;按需展开 数据库 → 表 / 视图 / 函数,表下分列 / 键 / 索引 / 触发器;查看数据、编辑结构、查看 DDL、添加和删除库 / 表、清空表;每个库一个 SQL 命令窗口(可评估执行计划);实例的监控面板、会话管理、锁定管理,修改账号密码 |
| 迁移 |
按源表一模一样地重建目标表(结构、索引、检查约束、表选项),可选按主键迁移数据(可按条件只迁移部分行);新表在临时表里建好、导入完才一步换上去 |
| 同步 |
源表、目标表都已存在,按字段名称对应、按主键 upsert 数据(INSERT … ON DUPLICATE KEY UPDATE),不改表结构 |
| 导出 |
把表结构(SHOW CREATE TABLE)和数据写成 SQL 脚本,保存到本机目录;数据按行数、文件大小自动拆成多个脚本(可按条件只导出部分行);任务下面列出每一份导出,可以一键恢复到表 |
迁移、同步、导出三页都是文件夹 → 任务的树,可以单独执行一个任务,也可以按顺序执行整个文件夹;导出页的任务下面还有一层:每一份导出。
所有配置保存在 ~/.x1/mysql-studio/conf.yaml(见「配置文件」)。设置项、命令面板里的命令见「设置项与命令」。和 PostgreSQL 版(PostgreSQL Studio)的差别集中写在「与 PostgreSQL 版的差异」。
数据源
添加 / 编辑数据源
「添加数据源」弹窗填写:名称、主机、端口、用户名、密码、默认数据库、SSL(disable / require / verify-full),可以先「测试连接」。默认值是 127.0.0.1 / 3306 / root;默认数据库可以留空(MySQL 的连接不必进库,树上照样列出全部库);名称留空时取「主机:端口」。require 只加密不校验证书,verify-full 校验证书和主机名。连接超时 15 秒。
数据源右键:编辑实例、复制实例(名称加「副本」)、测试连接、删除连接,监控面板、会话管理、锁定管理(见「实例监控」),系统安全 ▸ 修改密码…,添加数据库。「删除连接」只删除本地 conf.yaml 里保存的这条连接信息,不会删除服务器上的数据库和数据,以后可以重新添加;删除时会提示有几个迁移 / 同步 / 导出任务引用了它,删完关掉它名下开着的数据查询页、编辑表、添加数据库、删库确认、命令窗口和监控、会话、锁页。编辑保存后旧连接作废,命令窗口的会话断开,树上这个数据源重新加载。
对象树
树的每一层都是展开时才查询(都查 information_schema)。数据源 → 数据库 → 「表 / 视图 / 函数」三组(函数组里也列出存储过程);表下面分「列 / 键 / 索引 / 触发器」四组:列是字段(主键带黄色钥匙图标,日期时间字段图标为黄色,其余 NOT NULL 字段图标为蓝色,悬停看类型、默认值、备注),键是主键、唯一键、外键,索引是表上全部索引(含主键、唯一键);视图下面直接列出字段。系统库(mysql、information_schema、performance_schema、sys)默认不列出,设置 mysqlStudio.tree.showSystemDatabases 可以打开。
备注显示在名称后面(绿色);字段有默认值的,备注后面再接「默认 …」(生成列不算)。表的行尾按钮左边显示占用空间(如 32K,数据页加索引页);表悬停显示估算行数(InnoDB 的行数是统计值)、占用空间、存储引擎和「分区表」。树上看到的行数、大小是当前值(连接上把 information_schema_stats_expiry 设成了 0,不用 24 小时内的缓存)。
右键菜单和行尾按钮:
| 节点 |
右键菜单 |
行尾按钮 |
| 数据源 |
建立连接、重新连接、断开连接、编辑实例、复制实例、测试连接、删除连接、监控面板、会话管理、锁定管理、系统安全 ▸ 修改密码…、添加数据库 |
刷新(鼠标移入时显示) |
| 数据库 |
复制名称、查看 DDL、添加表、删除数据库 |
显示命令窗口、刷新 |
| 表 |
复制名称、查看数据、查看 DDL、编辑结构、构建查询 / 删除 / 更新 / 插入 / 插入(冲突时更新)语句、以此为源表新建迁移 / 同步 / 导出任务、清空数据、删除表 |
显示命令窗口、刷新 |
| 视图、函数 / 存储过程 |
显示命令窗口、查看定义、复制名称、刷新 |
查看定义、刷新 |
| 触发器 |
显示命令窗口、查看定义、复制名称 |
查看定义 |
| 视图的字段 |
显示命令窗口、复制名称 |
— |
| 分组节点,表下的字段、键、索引 |
— |
— |
「复制名称」:表、视图、函数复制「库.名称」,其余复制名称本身。分组节点没有菜单和按钮:别处新建了表、视图、函数,点库行尾的刷新就能看到,分组后面的数量也跟着更新;也可以选中某个分组按 F5 只刷新这一组;表下的分组按 F5 刷新的是表。
键盘:↑↓ 移动,←→ 收展,Enter / 空格切换收展,任何节点上 F5 刷新。双击表打开数据查询页,双击视图 / 函数查看定义,双击字段 / 键 / 索引复制名称。空白处右键「添加数据源」。
工具栏:搜索框(筛选的是已加载的节点)、全部刷新(重新读取配置文件,展开着的节点重新查询)、全部折叠、精简折叠(只收起表和视图下面的节点,上面几层保持展开),以及「更多」菜单:打开配置文件、重新加载配置、清除失效偏好、显示日志。四个页签的工具栏排法一样:搜索框右边第一个都是「刷新」,然后是收展按钮,最后是「更多」。
连接状态
参照 DBeaver:数据源图标右下角带绿点表示已连接。展开数据源、打开命令窗口 / 监控面板等用到它时自动连接;浏览用的连接池(每个数据源最多 3 条)空闲一分钟后自动断开,下次用时补上,状态不变。右键前三项按状态启用:
- 建立连接(未连接时):连上后展开它列出数据库;连不上就报错;
- 重新连接(已连接时):关掉这个数据源的全部连接(树和各页面用的连接池、命令窗口的会话)再连,用于网络断过、服务器重启过、连接卡住的情况;树上展开着的节点重新加载;
- 断开连接(已连接时):关掉全部连接,树上收起它。命令窗口、监控面板等页面留着:命令窗口下次执行时重新连接,监控面板、会话 / 锁定管理停止自动刷新,「建立连接」后接着刷新。
重新连接、断开连接之前:命令窗口正在执行语句的要先停止;有没提交事务的先问「提交」还是「回滚」。迁移、同步、导出任务用各自的连接,不受影响。
系统安全
右键「系统安全 ▸ 修改密码…」弹出窗口(同一个数据源只开一个),列出实例里的账号(mysql.user,每个账号是「用户名@主机」,'app'@'%' 和 'app'@'localhost' 是两个账号,密码各自独立),选一个,填新密码和确认密码。列表可以筛选;一行里标出数据源连接用的账号、系统账号(mysql.sys 之类,不能改)、已锁定、密码已过期、密码有效期、认证插件;没有权限改的置灰:自己的总能改,有 CREATE USER 权限(或 mysql 库的 UPDATE)的能改别人的。没有 mysql.user 的读权限时只列出当前连接用的账号。
用 SET PASSWORD [FOR 'u'@'h'] = '…' 修改,服务器按账号原来的认证插件(caching_sha2_password、mysql_native_password……)加密。语句里的密码是明文(没开 TLS 时网络上也是明文),服务器写 general log、slow log、binlog 时会把它改写成 <secret>;扩展的日志只记改了谁,不记密码。改自己的密码而账号要求验证当前密码(password_require_current)时多一个「当前密码」输入框(带 REPLACE '当前密码';有 CREATE USER 权限的特权账号不需要)。改的是数据源连接用的那个账号时,多一个「同时更新这个数据源保存的密码」勾选框(默认勾上),conf.yaml 里保存的密码跟着改;已经连着的会话不受影响,下次连接起用新密码。
查看 DDL 与定义
表的「查看 DDL」在编辑器里打开 SHOW CREATE TABLE 的原文(字段、键、索引、外键、检查约束、表选项、分区);数据库的「查看 DDL」是 SHOW CREATE DATABASE。视图、函数、存储过程、触发器的「查看定义」打开 SHOW CREATE VIEW / FUNCTION / PROCEDURE / TRIGGER 的原文,函数、存储过程、触发器用 DELIMITER $$ … $$ 包起来(体里有分号),可以直接在 mysql 客户端或命令窗口里执行。没有权限看定义时(不是定义者,也没有 SHOW_ROUTINE 权限)说明原因。
构建语句
表右键「构建查询语句」「构建删除语句」「构建更新语句」「构建插入语句」「构建插入语句(冲突时更新)」:按表的字段生成一条示例语句,追加到这个库的命令窗口末尾(命令窗口没开就先打开,跟前面的内容空一行)并选中它,改好再执行。表名不带库名(命令窗口已经在这个库里),名字需要时加反引号。
- 示例值按类型给:数字
0、BIT b'0'、YEAR YEAR(CURDATE())、文本 ''、ENUM 取第一个值、日期 CURRENT_DATE、DATETIME / TIMESTAMP NOW()、TIME CURRENT_TIME、JSON '{}'、二进制 X''、空间类型 ST_GeomFromText('POINT(0 0)')……认不出的类型写 NULL;
- 查询:列出全部字段(有备注的在后面注释),
LIMIT 100,可以直接执行;
- 删除、更新:按主键定位(没有主键用第一个唯一键;都没有时用第一个字段,并注释提醒把条件改成要处理的行)。更新的 SET 是主键、生成列以外的全部字段,每个后面注释类型和备注;
- 插入:生成列、自增字段这些自动生成的字段不写(开头注释说明);有默认值的写
DEFAULT,可以为空的写 NULL,必填的给示例值;值一行一个,后面注释字段名、类型、默认值、备注;
- 插入(冲突时更新):
INSERT … VALUES (…) AS new ON DUPLICATE KEY UPDATE 其余字段 = new.字段(8.0.19 起的写法)。主键字段即使是自增的也写进去;注意 ON DUPLICATE KEY 对表上每个唯一键都起作用。没有主键、唯一键,或者除了它们没有别的字段时,写成给主键赋自己的值(等于什么都不做)。
数据查询页
打开与工具栏
双击表名(或右键「查看数据」)在编辑区打开数据查询页,同一张表只开一个页。以表格分页显示表内容,默认每页 10 行;表头显示类型,有备注的字段在类型下面用绿色显示备注,主键带钥匙图标;值按服务器的文本输出原样显示(时间、DECIMAL、浮点跟 mysql 客户端一样),二进制显示成 0x 开头的十六进制,BIT 显示成二进制数字串,NULL 单独标出,换行显示成 ↵,单个值超过 2000 字符截断(悬停看开头);JSON 字段不截断。
上方工具栏:SELECT 按钮(展开字段勾选列表,勾选结果在执行查询时生效)、WHERE 按钮和 ORDER BY 按钮(见下面的「查询条件和排序」)、「查询」按钮,右边是「添加 / 删除 / 提交 / 撤销」(见「增删改记录」)。底部状态栏:左边是满足条件的总条数和这页的查询耗时,右边是首页 / 上一页 / 下一页 / 末页、每页行数(10 / 20 / 50 / 100 / 500)和「导出」。先按填写的排序,再按主键排序(没填排序就只按主键),翻页稳定;没有主键又没写排序时不排序。
查询在只读事务里执行(连接不开多语句,条件包在括号里),条件或排序里夹带别的语句、写数据都会被拒绝。每页最多等 30 秒(MAX_EXECUTION_TIME),超时提示加条件缩小范围;总行数最多统计 5 秒,超时则只能逐页翻,不能直接跳到末页。
查询条件和排序
WHERE、ORDER BY 两个按钮上显示实际参与查询的子句,点开是多行列表,每行是一个勾选框加一个输入框(写 WHERE / ORDER BY 后面的部分)。敲字段名时弹出字段联想(Ctrl+Space 也可呼出,↑↓ 选择、回车 / Tab 填入、Esc 关闭;是保留字或含特殊字符的字段名自动加反引号)。勾选的行拼进子句:条件多行时各自加括号、用 AND 连接;排序按从上到下的先后用逗号连接,行尾的上移 / 下移按钮或 Alt+↑↓ 调整先后。没勾选的行留作草稿,不参与查询但会保存。
注意:MySQL 默认把双引号括起来的 "name" 当字符串(不是字段名)。条件里照 PostgreSQL 的习惯写了双引号字段名时会提示改成反引号或不加引号。
选中、复制与粘贴
点格子选中它,点行号选中整行,点列标题选中整列,点左上角的 # 全选;Ctrl / ⌘ 点加选,Shift 点连选成方块,按住鼠标拖动也能连选;方向键移动,Shift+方向键扩大选区,Ctrl / ⌘+A 全选。选中后直接敲字(或回车 / F2)写进所有选中的格子;Ctrl / ⌘+C 复制成制表符分隔的文字(Excel 的写法);Ctrl / ⌘+V 跟 DataGrip 一样从选中区域的左上角铺开,行不够就自动添加新行;右键「复制」「粘贴」「设为 NULL」。不能改的格子(生成列、截断的值、标记删除的行等)和 NOT NULL 却要写 NULL 的格子会跳过,并说明原因。
增删改记录
点「添加」在表格末尾加一行输入框,格子里的灰字说明留空时写进去的是什么(自动生成 / DEFAULT / NULL / 必填)。tinyint(1) / bit(1)(MySQL 的 BOOL)字段是 1 / 0 / null 下拉,ENUM 字段是可选值下拉;DATE、DATETIME、TIMESTAMP 字段点格子弹出日历。双击已有记录的格子直接修改,回车记下、Esc 放弃、Tab 跳格,编辑框后面的「NULL」按钮设为 NULL。改过的格子标蓝,改回原值就不算改过。表没有主键时只能添加,不能修改、删除已有的行。
点「提交」在一个事务里先删、再改、后加:删除、修改按查询时的主键定位,修改只写改过的字段,每条必须正好删掉 / 改到一行(找不到就报错);任何一条失败整体回滚,报错里说明是第几条并在表格里标出那一行。等行锁最多 10 秒。值都按文本交给服务器、由服务器按字段类型解析。MyISAM 这类不支持事务的表写进去就回不去了,页面上会提示。
导出
状态栏最右边的「导出」:SQL INSERT、SQL UPDATE(按主键,没有主键不能选)、CSV(UTF-8 BOM、CRLF)、TSV。导出的是满足条件的全部行(不分页),字段、条件、排序按上次执行的查询;只读事务里流式读、边读边写,大表也不占多少内存;先写到 .part 临时文件,成功后才改名。SQL 文件开头写 SET NAMES utf8mb4;、SET time_zone = '+00:00';(TIMESTAMP 按 UTC 读出、写出,换了服务器也是同一时刻);值不经 JS 类型转换:数值直接写,二进制写成 X'…',BIT 写成 b'…',含反斜杠的字符串写成 _utf8mb4 X'…'(导入时 NO_BACKSLASH_ESCAPES 开没开都是同一个值),其余是字符串字面量;生成列不写;表名不带库名,在哪个库执行就写进哪个库。
用户偏好
手动执行且成功的查询,其显示的字段、条件行、排序行(连同草稿)按「数据源 / 数据库 / 表」存进 conf.yaml 的 tablePrefs,下次打开这张表自动填好并按它查询。
表结构
编辑表
右键表名「编辑结构」(表下的字段、键、索引节点也能打开)打开修改表结构的弹窗(仿 DataGrip 的 Modify Table):左边是结构树(表 → 列 / 键 / 外键 / 索引 / 检查),右边是选中项的属性,下面随改随预览将要执行的 SQL。
- 表:名称、备注、存储引擎、字符集、排序规则、行格式、自增值;
- 列:名称、类型(下拉联想 MySQL 类型)、无符号、自增、字符集、排序规则、默认值、ON UPDATE、NOT NULL、生成列(VIRTUAL / STORED 和表达式)、备注、不可见(8.0.23+);可以调整已有字段的顺序(
FIRST / AFTER);
- 键(主键 / 唯一键)、外键(目标表、目标列、ON UPDATE / ON DELETE)、索引(普通 / 唯一 / 全文 / 空间、字段或前缀长度、表达式、可见性)、检查约束(表达式、是否 ENFORCED)。
名字超过 64 个字符直接列为问题。约束、索引的定义改了就删掉重建;只改名称用 RENAME INDEX,只改可见性用 ALTER INDEX … VISIBLE / INVISIBLE,只改是否强制用 ALTER CHECK … ENFORCED。
MySQL 的 DDL 不能回滚(执行时隐式提交),所以尽量合成一条 ALTER TABLE(8.0 的原子 DDL:要么全改了,要么都没改):先单独一条删外键 → 一条 ALTER TABLE 改字段、键、索引、检查约束、表选项 → 一条加外键 → 最后 RENAME TABLE 改表名。多于一条时前面的成功、后面的失败,前面的改动已经生效(预览里写明)。等元数据锁最多 10 秒;有删字段这种丢数据的操作时要再点一次「确认执行」。
新建表
数据库右键「添加表」打开同一个弹窗,初始是表名 new_table 和一个 bigint unsigned 自增的 id 主键。生成一条 CREATE TABLE(字段、键、索引、外键、检查约束、表选项都写在里面,原子的)。
添加数据库
数据源右键「添加数据库」:名称(默认 new_database,重名时加序号)、字符集、排序规则(下拉联想)、默认加密(DEFAULT ENCRYPTION 'Y' / 'N',8.0.16+;留空跟服务器的 default_table_encryption)。弹窗下方随改随预览 CREATE DATABASE 语句。MySQL 的库就是模式:没有所有者、注释、模板,建好库就能直接建表。
删除数据库 / 表
右键菜单最下面(仿 DataGrip 的 Drop)。先查清楚要删的东西,在确认框里写明大小、里面有什么、谁依赖它、有几个迁移 / 同步 / 导出任务用到它,以及将要执行的语句:
- 删除表:MySQL 的
DROP TABLE 没有 CASCADE。别的表的外键引用它时删不掉,要先删掉那些外键(只删约束,不删那些表)——确认框里列出来,按钮变成「删除外键并删除表」;基于它的视图不会报错,删表后变成失效的视图(列出来提醒);表上的触发器随表一起删除;
- 删除数据库:连同里面的表、视图、存储过程、函数、触发器、事件一起删掉。为防误删,确认是单独的弹窗,要手工输入库名(不能粘贴)。别的会话占着里面的表(没提交的事务)时要等元数据锁,最多 10 秒;命令窗口的会话 USE 着这个库时先断开。删的是数据源的默认库时提示去编辑数据源换一个。
DDL 不能回滚,删除后不能恢复。
清空数据
表右键「清空数据」(仿 DataGrip 的 Truncate):
- 清空(TRUNCATE):删表重建,很快,自增值从 1 重新编号,不能回滚;被别的表的外键引用时 MySQL 不允许 TRUNCATE(就算那些表是空的),这时只有下面一种;
- 逐行删除(DELETE):在一个事务里,失败整体回滚,自增值接着原来的编号;引用它的行按各自外键的 ON DELETE 规则处理(CASCADE 一起删、SET NULL 置空、RESTRICT / NO ACTION 有引用的行时整体失败)。
确认框里写明两个按钮各自执行的语句。
命令窗口
仿 DataGrip 的 query console:点树上库、表行尾的「显示命令窗口」按钮,或在库下面的节点上右键「显示命令窗口」,在编辑区打开这个库的 SQL 命令窗口(标签名是 库@数据源),每个库一个,重复打开时切过去。
编辑器与联想
编辑器显示行号,MySQL 语法高亮,括号配对,查找替换,多光标、矩形选择,Ctrl+/ 注释选中的行。右键「数据拼接」「数据包装」把选中的每行加逗号 / 加单引号再加逗号,方便拼成 IN (…) 的列表。
联想敲字时自动弹出(点号后面不用敲字,Ctrl+Space 随时呼出):
- FROM / JOIN / UPDATE / INTO / TABLE / TRUNCATE / DESCRIBE 后面是当前库的表和视图、库名;
- SELECT / WHERE / ON / SET / ORDER BY 等后面是语句里引用的表的字段、表的别名、函数(内置函数、当前库的存储函数),其次是表;
别名.、表名. 后面是字段,库名. 后面是这个库的表、视图、函数,库.表. 后面是字段;CALL 后面是存储过程;
- 语句开头是 SELECT、INSERT INTO、SHOW、CREATE TABLE 之类;刚写完一个值后面是 WHERE、JOIN、AND 之类的关键字;DATABASE / USE 后面是库名;
CAST(… AS 后面是类型;
- 名字是保留字或含特殊字符时,填入时自动加反引号(在反引号里敲的连引号一起换掉);字符串、
# / -- / /* */ 注释、@ 变量里不联想;
- 执行了 CREATE / ALTER / DROP / RENAME / TRUNCATE 之后重新查,树上这个库(建库、删库时是整个数据源)也跟着刷新;执行了
USE 别的库 后,联想、翻页按新的当前库。
执行与会话
工具栏:「执行全部」(Ctrl / ⌘+Shift+Enter)、「执行选中」(Ctrl / ⌘+Enter,没有选中时执行光标所在的语句)、「评估」、「停止」(另开连接 KILL QUERY 当前语句,后面的不再执行)。语句按分号切分,字符串、反引号名字、注释里的分号不算;CREATE PROCEDURE / FUNCTION / TRIGGER / EVENT 的 BEGIN … END 体里的分号不算;也认 mysql 客户端的 DELIMITER $$ 写法(之后按 $$ 切,DELIMITER ; 改回来)。逐条执行,出错即停。
每个命令窗口独占一个会话,第一次执行时才连,连上就 USE 这个库。SET、用户变量、临时表、没提交的事务在两次执行之间都保留;会话在事务里时工具栏显示「事务未提交」和「提交」「回滚」按钮(MySQL 的语句出错不会让事务作废,照样可以提交)。关闭窗口时会话断开,没提交的事务回滚。
工具栏「事务:自动 / 手动」切换提交方式,默认取设置 mysqlStudio.console.manualCommit:
- 自动提交:每条语句执行完就生效;写了
START TRANSACTION 的到 COMMIT 才提交;
- 手动提交:每条语句执行前会话不在事务里就先
START TRANSACTION,点「提交」才真正提交。注意 MySQL 的 DDL(CREATE / ALTER / DROP……)会隐式提交当前事务,回滚不了;
- 不用
SET autocommit = 0 实现:从手动切回自动时,开着的事务不会被悄悄提交。
日志与结果集
- 日志:每条语句一行——时间、语句、耗时、结果(返回 / 影响了几行,服务端的说明如
Records: 3,Duplicates: 0),报错时写出 MySQL 错误号和 SQLSTATE(如 [1064 / 42000]),语法错误按 near '…' at line N 推出出错位置并在编辑器里标出;服务端的警告(SHOW WARNINGS)跟在结果后面;
- 结果集:返回数据的语句各一个结果集页签(CALL 存储过程可能返回好几个),表格只读。每个结果集最多取回 500 行(设置
mysqlStudio.console.maxRows,最多 10000),用会话的 sql_select_limit 限制(INSERT … SELECT、子查询不受影响;存储过程里的 SELECT 不受它限制,多出来的行读到就丢)。
结果集翻页与导出
全部取回了的结果集直接在取回的行里翻页;没取完的查询语句(SELECT、WITH、VALUES、TABLE 开头,语句里没有写数据、INTO、FOR UPDATE / SHARE、LOCK IN SHARE MODE)另开一个只读会话重新执行:先数总条数(最多 5 秒),翻到取回的行以外的页时加 LIMIT … OFFSET … 重新查(语句里没写 LIMIT 时直接加在后面,保留 ORDER BY;写了的包成子查询)。只读会话看不到命令窗口会话里的临时表和没提交的改动。结果集里有同名字段时不能重新执行(给字段起不同的别名就行)。
导出(CSV / TSV / SQL INSERT):全部取回了的直接写;没取完的查询语句另开只读会话重新执行、一批批写出。SQL INSERT 写进结果集字段来自的那张表(字段都直接取自同一张表时;生成列不写),否则表名写成 query_result。INSERT 文件开头的 SET time_zone 是读出数据的会话的时区(重新执行时统一用 UTC),导入时 TIMESTAMP 还是同一时刻。
评估(执行计划)
工具栏「评估」对光标所在 / 选中的那一条语句(SELECT、WITH、VALUES、TABLE、INSERT、REPLACE、UPDATE、DELETE)生成执行计划:
- 评估(EXPLAIN):
EXPLAIN FORMAT=TREE(MySQL 8.0.16+),只看估算,不执行语句;
- 评估并实测(EXPLAIN ANALYZE):MySQL 8.0.18+,实际执行一遍,看每个节点真实的耗时、行数、循环次数。语句包在事务里,执行完就回滚(会话已经在事务里时用保存点);MyISAM 等不支持事务的表上的写入、自增值的消耗回滚不了。
计划在结果区开「计划 1、计划 2……」页签,画成一棵树:节点说明照 EXPLAIN 的原文(Table scan on t、Index lookup on t using idx (a=1)、Nested loop inner join、Filter: (…)……),可以收起;只看计划时列「成本、估算行数」,实测时列「独占耗时、实际行数、估算行数、循环、成本」。独占耗时(只看计划时是成本)格子的底色条是这个节点自己占的比例,50% 以上标红、10% 以上标橙;实际行数跟估算差十倍以上标黄(统计信息可能过时,可以 ANALYZE TABLE);没执行到的节点写「未执行」。「复制计划」复制 EXPLAIN 的原文。计划页签最多留 10 个,方便对比改动前后。
内容自动保存
编辑器里的 SQL 每隔几秒写回 conf.yaml 的 consoles(按「数据源 / 数据库」),执行前、关闭窗口时、Ctrl / ⌘+S 时立即写,下次打开原样恢复。超过 1 MB 的内容不再自动保存。
实例监控
数据源右键「监控面板」「会话管理」「锁定管理」,在编辑区各打开一个标签页,同一个数据源的同一种页只开一个。会话列表、锁等待都是整个实例的。没有 PROCESS 权限时只看得到自己账号的会话;performance_schema 关着或没有读它的权限时锁等待查不了。
监控面板
仿 DBeaver 的 Dashboard,三张折线图:
| 图 |
曲线 |
来源 |
| 会话 |
活动、空闲、事务中空闲、总数 |
information_schema.PROCESSLIST 按 COMMAND 计数(Sleep 又在 InnoDB 事务里的是「事务中空闲」;不含后台线程和面板自己的连接) |
| 每秒语句数 |
全部、查询、增删改 |
SHOW GLOBAL STATUS 的 Questions、Com_select、Com_insert / update / delete / replace 之和,相邻两次采样的差除以间隔 |
| 缓冲池每秒读取 |
磁盘读取、缓存命中 |
Innodb_buffer_pool_reads(要从磁盘读的)和 Innodb_buffer_pool_read_requests − reads(命中),按差值算每秒多少页 |
采样间隔 1 / 2 / 5 / 10 秒,时间范围最近 1 / 5 / 15 / 30 分钟,可以暂停、清空;右边是 MySQL 版本、实例已运行多久。会话图的小结是「已用 / 最大连接数(max_connections)」,缓冲池图是时间范围内的命中率。实例重启过(Uptime 变小)时曲线断开,不拿重启前后的计数相减。
会话管理
PROCESSLIST 每个连接一行,带上 INNODB_TRX 里的事务:连接 ID、用户、客户端(主机:端口)、数据库、程序(客户端报上来的 program_name)、状态、命令 / 线程状态、持续(处在当前命令多久了)、事务(开始多久了)、语句。执行中的在前,事务中空闲的其次,空闲的最后。在等锁(行锁或元数据锁)的会话写出被哪些连接阻塞。「隐藏空闲」去掉 Sleep 又不在事务里的;「显示后台线程」列出事件调度器、复制线程之类。详情区显示事务 ID、锁住 / 改动的行数、隔离级别和完整语句。
「取消查询」= KILL QUERY(停止正在执行的语句,会话和事务保留),「终止会话」= KILL CONNECTION(断开会话,没提交的事务回滚);先弹框确认,逐个执行,没权限(不是自己的会话要 CONNECTION_ADMIN 或 SUPER)、会话已经不在的单独报出来。
锁定管理
每个在等锁的会话和挡住它的会话一行,等得久的在前:
- 行锁 / 表锁(InnoDB):
performance_schema.data_lock_waits + data_locks,对象写出库.表、索引、锁住的那行的主键值,模式如 X,REC_NOT_GAP;
- 元数据锁(DDL、
LOCK TABLES、FLUSH TABLES 之类在等):performance_schema.metadata_locks,按 MySQL 的元数据锁兼容矩阵只算真正冲突的持有者。
「取消等待者的查询」让等待者放弃等锁;「终止持有者」断开持有者,它的事务回滚、锁随之释放。持有者状态是「事务中空闲」时,多半是应用开了事务没提交。
迁移与同步
任务文件夹
「迁移」「同步」两页的工具栏有【新建文件夹】【新建任务】两个按钮,任务都放在文件夹里。单独执行一个任务(行上的 ▶),或执行整个文件夹(按文件夹内的顺序逐个执行,某个失败或被停止后后面的不再执行);拖动排序、上移下移、复制任务、复制文件夹(同页复制,或迁移 ⇄ 同步互相复制)、删除。行图标区分未执行 / 执行中 / 成功 / 失败 / 已取消,悬停看上次执行的记录。
新建任务
- 批量任务:源表是勾选列表,每张表建一个任务,任务名、目标表名都取表名;同步会逐表检查(目标库里有没有同名表、字段映射、主键、同步条件),有不合格的表就不让创建;源和目标是同一个库时不能创建;
- 单表任务:源表是下拉框,只能选一张;可以改任务名、目标表。迁移可以调整字段顺序、写迁移条件、预览 SQL;同步的目标表可以不同名,显示字段映射。
级联下拉是 数据源 → 数据库 → 表(没有模式这一层)。每批行数 1–50000,默认 1000。同一数据源,或主机、端口相同的两个数据源指向同一张表(表名不分大小写),都算「源表与目标表相同」,不能保存。
迁移
MySQL 的 DDL 会隐式提交、不能回滚,所以不在目标表上就地删了再建,而是:
- 读源表定义(
SHOW CREATE TABLE);有迁移条件时先用 EXPLAIN 校验条件;目标表被别的表的外键引用时拒绝(MySQL 删不掉被引用的表)——都在动目标库之前报错;
- 在目标库按源表定义(字段顺序按配置)建一张临时表
_studio_tmp_…:字段、主键、唯一键、索引、表选项(引擎、字符集、行格式、自增值、表备注、分区)。先建索引后插数据;
- 勾选「迁移数据」时,按源表主键分批多行 INSERT 写进临时表(有迁移条件时只读满足条件的行);
- 一条
RENAME TABLE 目标表 TO 旧表, 临时表 TO 目标表(原子的)换上去,再删掉旧表;
- 补检查约束(约束名在整个库里唯一,临时表上建会撞上旧表的);勾选「迁移外键约束」时补建外键,失败只提醒;
ANALYZE TABLE 目标表。
1~4 之间任何一步失败或取消,删掉临时表,目标表保持原样。外键、检查约束的名字含源表名的换成目标表名,跟目标库里已有的重名时加 _1 后缀。自增值:建表语句带着源表的 AUTO_INCREMENT,导入的数据更大时 InnoDB 自己往后拨。执行前默认弹窗确认(设置 mysqlStudio.migration.confirmBeforeRun 可关闭)。
同步
源表、目标表都要已存在。字段按名称对应(MySQL 的字段名不分大小写,这里也不分),两边字段顺序不同不影响;目标表独有的字段不写,源表独有的字段忽略,目标表的生成列由目标库自己算。
冲突键是目标表主键(必须在源表中有同名字段),INSERT … ON DUPLICATE KEY UPDATE。注意 ON DUPLICATE KEY 对目标表的每个唯一键都起作用:新行在别的唯一键上撞上已有的行时,更新的是那一行(表单里提醒)。
同步条件(可选):只同步源表中满足条件的行,写 WHERE 后面的部分,例如 flag_delete = 0、updated_at >= NOW() - INTERVAL 7 DAY AND status <> 'draft'。条件框里敲字段名时联想源表的字段;「检查条件」在源表上校验(EXPLAIN,只解析不执行)并报出满足条件的行数;执行前先校验,写错了在写入任何数据之前就报错。源库会话设为只读,连接不开多语句,条件包在括号里。
数据传输
迁移、同步共用的读写方式:
- 读:有主键时按主键 keyset 分页,每批一条独立语句,不在源库挂长事务(多列主键写成
(a > ?) OR (a = ? AND b > ?) 的展开形式,才走得了范围扫描);没有主键时流式读整张表;
- 写:一批拼成一条多行 INSERT,超过
max_allowed_packet 就拆成几条;
- 值不经 JS 类型转换:源库以文本(二进制是原始字节)读出、写成字面量,由目标库按字段类型解析。两边都是任务会话:
time_zone = '+00:00'(TIMESTAMP 不会差几个小时)、sql_mode = 'STRICT_ALL_TABLES,NO_AUTO_VALUE_ON_ZERO,NO_ENGINE_SUBSTITUTION'(写不进去就报错而不是静默截断;零日期、自增字段的 0 原样搬)、lock_wait_timeout = 30;
- 读下一批和写当前批同时进行;每一步写进「输出 → MySQL Studio」。
导出
「导出」页跟「迁移」页的用法一样(文件夹、批量 / 单表任务、执行文件夹……),一个任务一张表,没有目标表,换成「保存位置」:保存路径(本机绝对路径,可以 ~ 开头)、每个数据文件最多行数(默认 100000)、每个数据文件最大 MB(默认 50);可以写导出条件。
每次执行在「保存路径 / 库.表名」下新建一个以执行时间命名的目录:
~/mysql-export/
└── app.orders/
├── 20261011_093000/
│ ├── schema.sql 表结构:SHOW CREATE TABLE(含导出时的自增值)
│ ├── data_0001.sql 数据:多行 INSERT(每条最多 1000 行或 1 MB),一个文件一个事务
│ ├── data_0002.sql
│ ├── foreign_keys.sql 外键(表有外键时才有),放在数据之后执行
│ └── restore.sql 用 mysql 客户端一键恢复(SOURCE 按顺序执行上面的文件)
└── 20261012_093000/
- 脚本里的表名不带库名:在哪个库里执行就恢复到哪个库(
mysql -D 库名,或先 USE 库名);
- 每个脚本开头记下会话原来的设置,换成导出时的(
SET NAMES utf8mb4、UTC 时区、严格 sql_mode),结尾恢复;TIMESTAMP、零日期、自增字段的 0 都能原样导回;
mysql -h 主机 -P 端口 -u 用户 -p 库名 < restore.sql(在导出目录里执行,SOURCE 按当前目录找文件);
- 读源表在一个
WITH CONSISTENT SNAPSHOT 的只读事务里,导出的是同一时刻的快照;流式读、边读边写;先写进 .partial 临时目录,全部写完再改名。
导出列表与恢复
有导出的任务能展开,下一级是每一份导出(新的在前):导出时间、行数、数据文件个数、大小,只导出了部分行的标「部分数据」。
恢复到这个任务的源表(导出从哪张表来就恢复到哪张表),跟迁移一样先在临时表里做好再换上去:检查导出完整、建的是这张表 → 按 schema.sql 建临时表(检查约束先不建)→ 按顺序导入数据文件(一段段读,不把文件整个读进内存;脚本里的 SET、事务语句跳过)→ RENAME TABLE 换上去、删掉原表 → 补检查约束、执行 foreign_keys.sql(外键建不上只提醒)→ ANALYZE TABLE。换上去之前任何一步失败或取消,删掉临时表,原表保持恢复前的样子。表被别的表的外键引用时拒绝(MySQL 删不掉被引用的表)。另有「打开目录」「复制路径」「删除导出」(移到废纸篓)。
与 PostgreSQL 版的差异
| 方面 |
PostgreSQL 版 |
MySQL 版 |
| 层级 |
数据源 → 数据库 → 模式 → 表 |
数据源 → 数据库(就是模式)→ 表;命令窗口、偏好、任务都按库,不再有模式这一层 |
| 名字 |
双引号,区分大小写(不加引号转小写) |
反引号;字段名不分大小写,表名是否区分取决于服务器的 lower_case_table_names;最长 64 字符 |
| 字符串 |
标准 SQL |
双引号默认也是字符串;反斜杠是转义符(除非 NO_BACKSLASH_ESCAPES),所以含反斜杠的值一律写成 _utf8mb4 X'…' |
| 布尔 |
boolean |
tinyint(1) / bit(1)(页面上是 1 / 0 下拉) |
| 自增 |
serial / identity + 序列 |
AUTO_INCREMENT(迁移、恢复带着源表的自增值,导入更大的值时自动往后拨) |
| DDL |
能放进事务、能回滚 |
隐式提交、不能回滚:编辑表合成一条原子的 ALTER TABLE;迁移、恢复在临时表里做好再 RENAME TABLE 一步换上去 |
| upsert |
ON CONFLICT (主键) |
ON DUPLICATE KEY UPDATE,对每个唯一键都起作用 |
| 删表 |
CASCADE 连带依赖 |
没有 CASCADE:先删引用它的外键;视图变成失效的 |
| 清空 |
TRUNCATE … CASCADE / RESTART IDENTITY |
被外键引用时不能 TRUNCATE,只能逐行 DELETE |
| 执行计划 |
EXPLAIN (FORMAT JSON) |
EXPLAIN FORMAT=TREE / EXPLAIN ANALYZE 的文本树 |
| 停止语句 |
pg_cancel_backend |
KILL QUERY |
| 改密码 |
本机算 SCRAM 验证串 |
SET PASSWORD,服务器按认证插件加密(日志里改写成 <secret>) |
| 授权 |
建库、编辑表里可以授权 |
不提供(MySQL 的权限按账号管理,请用 GRANT 语句) |
| 备份 |
服务器端 pg_basebackup / pg_dump |
暂不提供(见「限制」) |
设置项与命令
设置(mysqlStudio.*):
| 设置项 |
默认 |
说明 |
ui.dialogWindow |
float |
数据源、任务、文件夹、编辑表、添加数据库、删库确认这些弹窗在独立窗口(float)还是编辑器标签页(tab)里打开 |
tree.showSystemDatabases |
false |
数据源树、命令窗口联想、任务表单里列出系统库(mysql、sys、information_schema、performance_schema) |
migration.confirmBeforeRun |
true |
执行迁移任务前弹窗确认(迁移会按源表重建并替换目标表) |
console.maxRows |
500 |
命令窗口执行时每个结果集最多取回的行数(1–10000) |
console.manualCommit |
false |
命令窗口新打开时用手动提交 |
命令面板(分类「MySQL Studio」):添加数据源、添加迁移任务、添加同步任务、添加导出任务、打开配置文件 conf.yaml、重新加载配置、显示日志。
配置文件
~/.x1/mysql-studio/conf.yaml(目录 0700、文件 0600,密码为明文):
version: 1
datasources:
- id: 3f2a9c1b7e04
name: 本地开发库
host: 127.0.0.1
port: 3306
user: root
password: secret # 纯数字的密码要加引号,不然会被 YAML 当成数字
database: app # 可以为空
ssl: disable # disable | require | verify-full
migrations: # 文件夹 → 任务;tasks 的顺序就是执行文件夹时的顺序
- id: 5c6d7e8f9a0b
name: 用户数据
tasks:
- id: 9b1c2d3e4f50
name: users → users_bak
source: { datasourceId: 3f2a9c1b7e04, database: app, table: users }
target: { datasourceId: 3f2a9c1b7e04, database: archive, table: users_bak }
columnOrder: [id, name, email, created_at]
migrateData: true
where: 'flag_delete = 0' # 可选:迁移条件
includeForeignKeys: false
batchSize: 1000
lastRun: { at: '2026-09-23T12:00:00.000Z', status: success, rows: 2480, durationMs: 1830 }
syncs:
- id: 1a2b3c4d5e6f
name: 报表
tasks:
- id: 0a1b2c3d4e5f
name: orders 同步
source: { datasourceId: 3f2a9c1b7e04, database: app, table: orders }
target: { datasourceId: 3f2a9c1b7e04, database: report, table: orders }
where: 'flag_delete = 0' # 可选:同步条件
batchSize: 1000
exports: # 一个导出文件夹都没有时不写这个键
- id: 6d7e8f9a0b1c
name: 每日导出
tasks:
- id: 2b3c4d5e6f70
name: orders
source: { datasourceId: 3f2a9c1b7e04, database: app, table: orders }
outputDir: ~/mysql-export
rowsPerFile: 100000 # 每个数据文件最多行数,0 不限
mbPerFile: 50 # 每个数据文件最大 MB,0 不限
batchSize: 1000
tablePrefs: # 数据查询页的用户偏好
- datasourceId: 3f2a9c1b7e04
database: app
table: orders
select: [id, status, amount]
conditions:
- text: status = 'paid'
enabled: true
sorts:
- text: created_at DESC
enabled: true
consoles: # 命令窗口的内容:每个库一份
- datasourceId: 3f2a9c1b7e04
database: app
sql: |
SELECT * FROM orders WHERE status = 'paid';
可以手工编辑,保存后插件自动重新读取。写回时先写临时文件再改名;exports、tablePrefs、consoles 为空时不写;不认识的顶层键原样保留。数据源缺 name 取主机名,缺 port / user 取默认值;缺必填字段的条目忽略并记进日志。无法解析的文件会被改名为 conf.yaml.broken-<时间>。PostgreSQL 版的 conf.yaml 不通用(引用里没有库、表的结构不同),两个扩展的配置文件各自独立。
限制
- 支持 MySQL 8.0 及以上(
EXPLAIN FORMAT=TREE 要 8.0.16+,EXPLAIN ANALYZE 要 8.0.18+,AS new 的 upsert 写法要 8.0.19+);MariaDB 没有测试过;
- 没有服务器端备份:PostgreSQL 版的「备份」页靠
COPY … TO PROGRAM 让服务器执行 pg_basebackup / pg_dump,MySQL 没有通过 SQL 在服务器上执行程序的办法,这一页暂未提供;
- 导出只能恢复到原来那张表;被别的表的外键引用的表不能迁移覆盖、不能恢复(要先删掉那些外键);
- 事务只对 InnoDB 这类事务引擎有效:MyISAM 表上的数据页提交、逐行删除、EXPLAIN ANALYZE 的写入回滚不了;
- 迁移、恢复期间目标库里会短暂出现
_studio_tmp_… 临时表,失败时删掉;被强行中断(比如 VS Code 崩溃)时可能残留,可以手工删除;
- 表的触发器、权限不迁移、不导出;视图、存储过程、函数、事件不在迁移、导出范围内;
- 修改密码的语句里密码是明文,没开 TLS 时网络上也是明文(服务器日志里会改写);
- 浏览用的连接每个数据源最多 3 条,空闲 60 秒断开;迁移、同步、导出、命令窗口各自另开连接。
开发
npm install
npm run compile # 开发构建
npm run watch # 监听构建(F5 调试用)
npm run check-types # 类型检查(扩展 + webview)
npm test # 配置存储 + SQL 切分与联想、命令窗口逻辑、导出格式 + 导出 / 恢复引擎(假连接)+ 系统安全 + webview 前端(jsdom)
npm run package # 类型检查 + 生产构建
./build.sh # 打包 VSIX,放进 release/(也可以 npm run vsix)
npm run dev:link # 把本仓库软链到 ~/.vscode/extensions,重载窗口即生效(dev:unlink / dev:status / dev:check)
python3 scripts/render-icon.py # 改了 media/icon.svg 后重新生成 media/icon.png(仅 macOS,借用无头 Google Chrome 渲染)
测试不连真实数据库:要连接的地方用假的连接(记下发出的语句、按语句给预设的结果)。