第 04 章编辑数据

这一章是整个工具里最需要小心的部分。核心是两句话: 数据网格是只读的,所有编辑都在右侧字段栏里做你在界面上做的任何修改都不会立刻写进数据库。 改动先进暂存区,你确认后再一次性提交。

为什么要有暂存区

如果每改一个字段就发一条 UPDATE,那你没有任何反悔的机会—— 手滑改了生产库的一列数据,唯一的补救办法是去翻备份。

另一条同样重要的规矩:数据网格只读。网格只用来浏览和选择,改值一律在右侧的字段栏里做。 这样「看」和「改」是两件分开的事,拖选、滚动、双击都不会误改到数据。

把改动先攒起来,能带来三件好处:

  1. 可以预览。提交前能看到将要执行的确切 SQL,确认是改这 7 行而不是那 7 行。
  2. 可以整体放弃。一键回到服务器上的真实数据,什么都不用管。
  3. 要么全成要么全不成。所有改动打包在一个事务里,中间的语句失败就整体回滚,不会留下改了一半的状态。
按钮上的数字

工具栏「提交修改」按钮右边始终显示待提交的改动条数。它是 0 的时候整个按钮组是灰的, 所以「现在有没有没提交的东西」这个问题,扫一眼工具栏就知道。

什么时候能编辑

一张表要能编辑,必须先能被可靠地定位到某一行。不满足条件的表会被明确地置为只读。

表的条件能不能编辑状态栏会说什么
有主键可以
有唯一索引,且索引列都不可为 NULL可以
唯一索引的列可以为 NULL不能唯一索引的列可以为 NULL,无法可靠定位行
没有主键也没有唯一索引不能该表没有主键或唯一键,无法安全定位行
是视图不能视图不可编辑
连接处于只读模式不能该连接处于只读模式
不可编辑时的表现
  • 「插入行」按钮直接隐藏,不是变灰
  • 「删除行」禁用
  • 右侧字段栏仍然显示这一行的字段和值,但所有编辑器禁用,栏顶写明原因
  • 双击单元格改成打开快速查看(内容还是能看全的)
  • 状态栏把原因写清楚,而不是留一堆灰按钮让你猜

在右侧字段栏里改字段

数据网格是只读的:不能在格子里改值,也没有「双击进入编辑」这回事。 所有字段值的修改都在右侧的字段栏里做——它是表数据视图里唯一的编辑入口。

字段栏长什么样

选中一行后,右侧的字段栏把这一行的所有列按表定义的顺序纵向列出来,一行一个字段。

右侧字段栏的结构 users 行 2 · 已选 1 行 · 没有未提交的改动 放弃 预览 提交修改 # 🔑 id name email content 1 1 张三 zs@example.com 这是一篇很长的文… 2 2 李四 ls@example.com «BLOB 12.3 KB» 3 3 王五 NULL NULL 🔒 网格只读:不能在格子里改值, 也不能把多行文本铺开成二维表格粘进来。 双击单元格 = 跳到字段栏里对应的字段 🔍 搜索字段 18 个字段 🔑 id int unsigned 42 name varchar(64) 李四 email varchar(255) ls@example.com content longtext 这是一篇很长的文章, 里面还有换行。 展开 status enum('draft','published') draft 1 2 3 4 5 6 7
图 4-1 右侧字段栏。左边是只读的数据网格,右边是这一行所有字段的编辑器。
#元素含义
1工具栏最右的 ▤ 显示 / 隐藏右侧字段栏。默认显示,没有快捷键;也可以走菜单「视图 → 显示 / 隐藏右侧字段栏」。
2只读的数据网格 网格只负责浏览和选择:不能在格子里改值,也不能把多行文本铺开成二维表格粘进来。在网格里双击一个单元格,会自动跳到字段栏里对应的字段并聚焦;表不可编辑时则改为打开快速查看。
3搜索字段 按列名过滤字段。右侧显示命中的数量,例如 显示 3 / 18;没有过滤时显示字段总数。
4列名 + 类型 字段按表定义的顺序排列,不受网格的列显隐与列顺序影响。主键列的列名前带 🔑 并且加粗。类型是完整类型名(varchar(255)int unsigneddecimal(10,2)),只读,不做类型转换。
5值编辑器 点进去就能改这个字段。编辑器按列类型自动选,不用你挑(见下面两张图)。
6∅ 按钮 点一下把这个字段设为 NULL,再点一下恢复原来的值。值本来就是 NULL 时按钮呈按下状态。
7拖拽把手 拖它调整字段栏的宽度,范围 260–560pt(默认 320pt),按表记住。

编辑器按列类型自动选

点字段里的值就进入编辑,编辑器由这一列的类型决定。

文本类字段的编辑器 普通文本 varchar / char / text name varchar(64) 李四 单行输入框,点进去就能改。 ↩ 或失去焦点写入暂存区,Esc 放弃。 Tab / ⇧Tab 提交并移到下一个 / 上一个字段。 8 长文本 / JSON longtext / json content longtext NULL {  "title": "标题",  "body": "正文…" 展开 ⇧↩ 换行,⌘↩ 提交;Esc 放弃。 点「展开」进入大编辑窗口:带行号与查找。 9 10 数字 int / decimal / float price decimal(10,2) NULL 128.00 单行输入框,数字靠右对齐,方便比大小。 只接受合法数字(含负号和小数点)。 校验不过时输入框抖动,编辑状态不退出。 11 日期 / 时间 date / datetime / timestamp created_at datetime NULL 2025-01-01 10:00:00 📅 2025 年 1 月 293031 234 567 8910 11 121314 151617 18 1 选完日期后,时间部分 可以继续手输。 时间戳 / datetime 一律 按文本解析,避免时区歧义。 12
图 4-2 文本 / 数字 / 日期的值编辑器。每张卡片就是字段栏里的一个字段行。
#列类型用什么改
8普通文本 单行输入框。
9长文本 / JSON 多行输入框,可以换行。按 ⇧↩ 换行不会提交,⌘↩ 才提交。
10「展开」 把多行编辑器放大成一个独立的大窗口,同样带行号与查找,长内容在这里改更舒服。
11数字 单行输入框,右对齐。只接受合法数字(含负号和小数点)。
12日期 / 时间 日期选择器 + 手输。时间部分也可以直接打进去;时间戳这类值一律按文本解析,避免时区看错。
选项类字段的编辑器 ENUM 取值固定的一组 status enum('draft','published') published draft published archived deleted 限定了取值, 不需要手输, 也不会拼错。 13 SET 可以同时取多个值 perms set('read','write','delete') read,write read write delete admin 勾中的值用逗号 拼在一起写入。 值必须来自列定义。 14 布尔 偏好开启时才这样 active tinyint(1) 开启(1) 偏好里打开「把 tinyint(1) 显示为复选框」之后, 这一列在这里就是一个复选框。 没打开时按普通数字处理,和上面一样手输。 15 BLOB / 二进制 不能当文本编辑 photo mediumblob NULL «BLOB 12.3 KB» 查看 从文件导入… 设为 NULL 二进制没法安全地当文本改,所以只给这三个操作。 大字段还没取完整值时,点「查看」才去服务器取。 16 17 18
图 4-3 ENUM、SET、布尔、BLOB 的值编辑器。
#列类型用什么改
13ENUM 下拉选择。取值来自列定义,不需要手输,也不会拼错。
14SET 多选。勾中的值用逗号拼在一起写入;值必须来自列定义。
15布尔(tinyint(1) 偏好里打开「把 tinyint(1) 显示为复选框」之后,这一列就是一个复选框;没打开时按普通数字手输。
16查看 打开快速查看。大字段还没取完整值时,点它才去服务器取。
17从文件导入… 把文件内容作为这个字段的新值(二进制原样读入)。
18设为 NULL BLOB 的 ∅。二进制不能当文本改,只能整个设为 NULL;再点一次恢复原值。

提交这次编辑

按键 / 动作结果
失去焦点,或 把这个值写进暂存区(不会立刻落库)
Tab / ⇧Tab提交并移到下一个 / 上一个字段
Esc放弃这次编辑,恢复原值
⇧↩多行编辑器里换行,不提交
⌘↩多行编辑器里提交
⌘V把剪贴板里的纯文本贴成这一个字段的值(见下面「粘贴」)

把字段设为 NULL

设为 NULL ① 原来有值 email varchar(255) zs@example.com ∅ 没有按下。 值还是服务器上的原值。 ② 点一下 ∅ email varchar(255) NULL 值变成 NULL,按钮呈按下状态。 NULL 和空字符串不同:空字符串是一格空白。 ③ 再点一下 ∅ email varchar(255) zs@example.com 恢复到刚才那个值。 没有记录时,恢复成空文本。 19 20 21
图 4-4 每个字段右侧的 ∅ 按钮:点一下设为 NULL,再点一下恢复原来的值。
#元素含义
19∅(设为 NULL) 点一下把这个字段的值设成 NULL。这一步同样只进暂存区,提交时才落库。
20∅(再点一次) 恢复到刚才那个值;没有记录时恢复成空文本。从 NULL 恢复不需要重新去服务器取值。
21NULL 与空字符串 NULL 显示成灰色的 NULL,空字符串显示成一格空白。它们是两个不同的值,别当成一回事。

校验

外观反馈:改完之后会看到什么

改回原值

把某个字段改回原来的值,这个字段的修改标记会自动消失; 如果一行里所有改动都被改回去了,这一行就重新变成「干净」的。

插入、复制、删除行

三种行状态 # 🔑 id name email status updated_at 1 1 张三 zs@example.com active 2025-01-01 2 2 李四 ls@example.com published 2025-01-02 (空) 赵六 zl@example.com draft 留空 = 用数据库默认值 4 4 王五 ww@example.com deleted 2025-01-04 5 5 钱七 qq@example.com active 2025-01-05 新增的行 绿色底色,提交前可以按 Esc 取消这一行。自增主键留空,由数据库生成。 修改的行 改过的格子橙色底色带小三角,行号列也有标记。 删除的行 整行加删除线并变淡,但还没真正删除。删除前会显示将要执行的条件,例如 WHERE id = 42。 已删除的行 不能再编辑。想改的话先在字段栏里点「撤销删除」。
图 4-5 网格里三种行状态的样子,以及行号列上的对应标记。

插入行

复制行

删除行

字段栏的三种特殊状态 新增行 Esc 取消这一行 id int unsigned 留空,由数据库生成 name varchar(64) 赵六 email varchar(255) zl@example.com 光标停在第一个可编辑字段上, Tab 往下依次填。 22 这一行已标记删除 撤销删除 id int unsigned 42 name varchar(64) 王五 email varchar(255) ww@example.com 字段全部只读并变灰。 想改要先点「撤销删除」。 23 该表没有主键或唯一键,无法安全定位行 id int unsigned 42 name varchar(64) 王五 email varchar(255) ww@example.com 字段和值照常显示, 但编辑器全部禁用。 24
图 4-6 字段栏在三种特殊情况下的样子:新增行、已标记删除、表不可编辑。
#情况字段栏里是什么样
22新增行 栏顶标注「新增行」,字段全部为空,光标在第一个可编辑字段上。留空的列交给数据库默认值,自增主键留空即可。
23已标记删除 栏顶显示 这一行已标记删除 和「撤销删除」按钮;字段全部变灰、只读。想改要先撤销删除。
24不可编辑 栏顶写明原因(比如 该表没有主键或唯一键,无法安全定位行);字段和值照常显示,但所有编辑器禁用。双击单元格改为打开快速查看。

粘贴

字段栏里也支持粘贴,但只贴一个字段:把光标放进某个字段的编辑器,按 ⌘V, 剪贴板里的纯文本就成为这一个字段的值,然后走和手工输入一样的校验。

网格上不再支持把带制表符 / 换行的多行文本铺开成二维表格——网格是只读的。 需要批量改数据时,去 SQL 编辑器里写 UPDATE

为什么只留单字段粘贴

二维铺开粘贴要求网格可编辑,而我们把网格整个设成了只读:这样拖选、滚动、双击都不会误改数据。 批量改动走 SQL 编辑器更可控,也能在提交前看清语句。这是刻意的简化,不是漏掉的。

暂存区的规则

暂存区属于当前标签。每个表数据标签各有一个,互不影响。切到别的标签不会丢内容。 在同一个标签里,同一行最多只会有一条待提交的改动。

已有新操作结果
改某列记为「修改」
已改 A 列再改 B 列合并成一次「修改」
已改 A 列把 A 列改回原值这一行的改动消失
新增行记为「新增」
新增行继续改这一行合并进「新增」
新增行又删除这一行两者都消失,不产生任何操作
删除行记为「删除」
已改某行又删除该行「修改」被「删除」取代
已删除某行试图编辑该行拒绝,提示先撤销删除

右键某一行 → 撤销该行的修改,可以只回退这一行,别的改动不受影响。

预览

⇧⌘P 或点工具栏「预览」,看看将要执行什么。

预览 SQL 面板 将要执行的 SQL(7 条) 先 INSERT 1 INSERT INTO `app_dev`.`users` (`name`, `email`) VALUES ('赵六', 'zl@…'); 再 UPDATE 2 UPDATE `app_dev`.`users` SET `email` = 'a@b.c' WHERE `id` = 42; 3 UPDATE `app_dev`.`users` SET `status` = 'published' WHERE `id` = 7; 最后 DELETE 4 DELETE FROM `app_dev`.`users` WHERE `id` = 7; …(共 7 条) 这些语句会被包在一个事务里执行: START TRANSACTION; … COMMIT; 复制全部 在新查询标签中打开 关闭 提交
图 4-7 预览面板。语句按「先插入、再修改、最后删除」排序。
预览和实际提交完全一致

面板里显示的语句就是实际会下发的语句,不存在「预览是一套、执行是另一套」的情况。 这一点是刻意设计的:所有写入都直接生成 SQL 文本,不经过参数化改写。

把鼠标移到预览里的某一条语句上,网格里对应的那一行会高亮,方便你确认。

提交

⌘S 或点工具栏「提交」。提交过程是这样的:

  1. 界面锁定编辑,状态栏显示 正在提交 3/7…
  2. 所有改动在一个事务里依次执行
  3. 全部成功 → 清空暂存区,重新加载当前页,状态栏短暂显示 已提交 7 处修改,耗时 128 ms
  4. 任何一条失败 → 整个事务回滚,所有改动都没生效,弹出错误面板
提交失败面板 提交失败 第 2 条语句执行失败 [错误 1062] SQLSTATE 23000 Duplicate entry 'a@b.c' for key 'uniq_email' ↑ 服务器返回的原文,不翻译、不改写、不截断 出错的那条语句: UPDATE `app_dev`.`users` SET `email` = 'a@b.c' WHERE `id` = 42; 事务已回滚,你的修改都还在暂存区里,尚未生效。 放弃全部修改 关闭并修正
图 4-8 提交失败面板。三条必须说清楚的信息:错在哪、原文是什么、数据现在是什么状态。
失败后暂存区不会被清空

提交失败时你的修改原样保留在暂存区,可以修正后再提交。 因为事务已经整体回滚,数据库里什么都没变,所以这时也不需要「回退」什么。

提交过程中的其他规矩

某条语句影响 0 行怎么办

算成功

如果某条 UPDATE / DELETE 影响 0 行(比如这一行已经被别处删掉了), 视为成功,不报错。Console Log 里会记一条「影响 0 行」, 提交完成后这一行会从刷新后的数据里消失。

这样做是有意的:如果你在改一条别人刚删掉的数据,弹一个错误出来只会打断你, 反正结果(那一行不存在)和你想的一样。

放弃

有未提交改动时的保护

下面这些操作会先问你,避免手一滑把攒了半天的改动弄丢:

未提交改动确认弹窗 有未提交的修改 当前标签有 7 处未提交的修改。 取消 放弃修改 提交并继续 关闭标签 脚本有未保存的改动,关闭前保存吗? 取消 不保存 保存并关闭 按钮措辞必须具体。不要用「确定 / 取消」这种分不清谁是谁的说法。
图 4-9 两个确认弹窗。左边是表数据的未提交改动保护;右边是关闭关联过磁盘文件的查询标签时(纯草稿不提示)。

一个必须知道的限制:不检测并发修改

最后写入者生效

TableLite 不检测「这一行在你打开页面之后是否被别人改过」。 提交时直接用你本地的值覆盖服务器上的当前值。

也就是说:如果你在 10:00 打开一行、10:05 才提交,而别人在 10:02 改过同一行, 那么别人的改动会被你覆盖掉,而且不会有任何提示。

这是刻意的取舍——要检测就得给每张表加版本列或者比对整行原值, 对一个自用工具来说不划算。用在多人同时在写的表上时请自己注意。