SQL——-已更新或删除的行值要么不能使该行成为唯一行,要么改变了多个行

本文介绍了一种在数据库中因未设置主键而导致的问题及解决方案。通过简单的步骤,包括新建查询并执行特定的SQL语句,可以有效地解决该问题。强调了在数据录入之前设计好主键的重要性。

【报错】 

【原因】

没有设置主键

【解决方案】

点击“新建查询”

delete Login.dbo.Scores where UserName='admin'

运行即可

【结束】

所以大家一定要在写数据前设计好主键。

 

非常好的问题!你已经理解了“删除数据时索引会自动同步删除”,现在你想深入底层:**在使用 WAL(Write-Ahead Logging)模式下,SQLite 是如何保证数据和索引的一致性同步的?** 我们来详细解析这个过程。 --- ## ✅ 总览:WAL 模式下的 DELETE 过程 当 SQLite 使用 **WAL 模式**时,`DELETE` 操作不会直接修改主数据库文件,而是先写入一个叫 `wal` 文件中。整个过程中,**数据页和索引页的变更都会被记录为日志条目(log records)**,并通过原子性的事务机制确保一致性。 ### 🧱 核心结论提前说: > 在 WAL 模式下,**当你执 `DELETE FROM table WHERE ...` 时,SQLite 会在 WAL 文件中依次记录:** > > 1. 对主表对应的数据页的修改(标记为删除) > 2. 对所有相关索引页的修改(移除该行对应的索引项) > > ✅ 所有这些更改作为一个事务提交,要么全部生效,要么全部不生效 —— **从而保证数据与索引完全同步。** --- ## 🔍 WAL 模式简介(背景知识) 默认情况下,SQLite 使用 **rollback journal** 来实现事务。而启用 WAL 后: ```sql PRAGMA journal_mode = WAL; ``` - 写操作不再锁定整个数据库。 - 修改的内容先写到 `.sqlite-wal` 文件中。 - 主数据库文件保持只读,直到 `CHECKPOINT` 将变化刷回。 - 支持更高的并发读写性能。 --- ## 📦 DELETE 操作的完整流程(WAL 模式) 假设你有如下结构: ```sql CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT, email TEXT ); CREATE INDEX idx_users_email ON users(email); ``` 你要执: ```sql BEGIN; DELETE FROM users WHERE email = 'alice@example.com'; COMMIT; ``` 下面是 SQLite 底层发生的事情: --- ### 步骤 1:开启事务并进入 WAL 模式 ```sql BEGIN; ``` - SQLite 创建复用 `database.db-wal` 文件。 - 开始将所有修改追加写入 WAL 文件,而不是原数据库文件。 --- ### 步骤 2:查找目标及其索引项 SQLite 执查询计划: 1. 使用 `idx_users_email` 索引快速定位 `email='alice@example.com'` 的; 2. 找到对应的 `rowid` `id`; 3. 定位主表中该行所在的 **数据页(data page)**。 --- ### 步骤 3:修改主表数据页(标记删除- SQLite 从磁盘加载包含该行的 **主表数据页** 到内存。 - 在内存中将该行标记为“已删除”(通过设置 record header 中的 flag)。 - 把这个修改后的页面内容作为一条 **WAL 日志记录(frame)** 写入 `.wal` 文件。 📌 WAL 文件中新增一条记录: ``` Page No: 5 (假设是 users 表的数据页) Data: [原始页] -> [删除了 alice 的那条记录] ``` --- ### 步骤 4:删除索引中的条目 对于每一个与该行相关的索引(如 `idx_users_email`): 1. 查找索引中键为 `'alice@example.com'` 的条目; 2. 找到其指向的 `rowid` 是否匹配; 3. 从索引页中删除该条目; 4. 将修改后的索引页也写入 WAL 文件。 📌 WAL 文件再增加一条记录: ``` Page No: 8 (假设是 idx_users_email 的索引页) Data: [原始索引页] -> [移除了 'alice@example.com' -> rowid=X 的条目] ``` ✅ 注意:这两个修改(数据页 + 索引页)都属于同一个事务,会被连续写入 WAL,并带有相同的 **commit record 标记**。 --- ### 步骤 5:提交事务(原子性保障) ```sql COMMIT; ``` SQLite 执以下动作: 1. 在 WAL 文件末尾写入一个特殊的 **commit record**(包含最终的 WAL frame 编号); 2. 这个 commit 记录标志着事务成功完成; 3. 其他连接现在可以看到这次删除的结果(MVCC 多版本控制); 💥 如果中途崩溃? - 只要没写完 commit record,下次启动时会忽略未完成的事务。 - 数据和索引都不会被部分更新 → **一致性得以保证**。 --- ### 步骤 6:CheckPoint(可选)—— 将变化刷回主数据库 后续某个时刻(手动自动)触发 checkpoint: ```sql PRAGMA wal_checkpoint; -- 自动进 ``` - SQLite 将 WAL 文件中已提交的页面(包括数据页和索引页)合并回主数据库文件 `database.db`; - 清理 `.wal` 文件内容。 此时,主库文件也包含了最新的删除状态。 --- ## 🔄 为什么能保证“数据和索引同步”? | 机制 | 如何保证一致性 | |------|----------------| | 🧩 单一事务 | 数据页和索引页的修改都在同一个事务内完成 | | ⚛️ 原子提交 | 只有当所有修改都写入 WAL 并加上 commit 标记后才算成功 | | 🗃️ 页面级日志 | 每个被修改的页(无论是表还是索引)都单独记录在 WAL 中 | | 🔁 回放机制 | 崩溃恢复时,SQLite 会重放 WAL 中的所有帧,重建完整状态 | > 💡 所以:**即使系统在删除索引之后、删除数据之前突然断电,由于事务未提交,整个操作都会回滚。** --- ## 🛠️ 验证工具:查看 WAL 内容(调试用途) 你可以使用 SQLite 提供的命令工具查看 WAL 文件内容(需关闭数据库连接): ```bash # 使用 hexdump 查看(粗略) hexdump -C yourdb.sqlite-wal | head # 者用 sqlite3 命令(实验性) sqlite3 yourdb.db ".dump" ``` 更专业的做法是使用 [SQLite Database Browser](https://github.com/sqlitebrowser/sqlitebrowser) 自定义 C 程序解析 WAL 文件格式。 --- ## ⚠️ 性能提示 | 场景 | 建议 | |------|------| | 大量删除 | 考虑批量提交,避免频繁 I/O | | 高频索引更新 | 减少不必要的索引,提升 DELETE 性能 | | WAL 文件过大 | 设置 `PRAGMA wal_autocheckpoint = 1000;` 自动清理 | --- ## ✅ 总结 | 问题 | 回答 | |------|------| | 删除数据时索引怎么同步? | SQLite 自动在同一个事务中修改索引页并写入 WAL | | 使用 WAL 会影响一致性吗? | 不影响,WAL 支持 ACID,能保证原子性和持久性 | | 数据和索引会不同步吗? | ❌ 不可能,除非数据库损坏 | | 崩溃后怎么办? | 启动时自动回放 WAL,恢复一致状态 | | 是否需要干预? | ✅ 完全不需要,SQLite 全自动管理 | ---
评论 13
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值