# SVN工作副本数据库损坏的深度修复指南:从应急替换到表结构重构
正午的阳光透过百叶窗,在键盘上投下斑驳的光影。你刚完成一个关键模块的修改,准备提交到SVN仓库,指尖在回车键上悬停的瞬间,终端却弹出一行冰冷的错误:`svn: E200030: database disk image is malformed`。心跳漏了一拍——这意味着你的工作副本数据库文件(`.svn/wc.db`)已经损坏,所有未提交的更改、本地状态信息都可能面临丢失风险。这种场景对于依赖版本控制的开发者来说,无异于一场小型灾难。
`database disk image is malformed`这个错误信息,本质上是SQLite数据库引擎在读取`.svn/wc.db`文件时遇到了结构异常。SVN使用SQLite作为工作副本的元数据存储引擎,这个轻量级数据库记录了文件版本、修改状态、合并历史等关键信息。当磁盘写入中断、系统崩溃、存储介质故障,甚至某些杀毒软件的误操作发生时,都可能破坏这个数据库文件的完整性。面对这种情况,很多开发者的第一反应是“重新检出”——但这意味着放弃所有本地修改,对于没有及时提交的代码来说,代价过于沉重。
本文将带你深入SVN工作副本的内部机制,提供一套从**紧急应对**到**深度修复**的完整解决方案。不同于网络上零散的技巧分享,我们将按照修复的复杂度和风险等级,构建三个层次的应对策略:**快速文件替换法**适用于团队协作环境下的紧急恢复;**SQLite3命令修复**能处理大多数中度损坏;而**数据库表重构方案**则是挽救严重损坏的终极手段。更重要的是,我们还会探讨如何在修复失败时,通过巧妙的版本回退技巧最大限度保留工作成果,并附上一份精心整理的SQLite3实用命令速查表,让你在未来的运维中游刃有余。
## 1. 理解SVN工作副本的数据库架构与损坏成因
在深入修复之前,我们需要先理解`.svn/wc.db`这个文件到底存储了什么,以及它为何如此脆弱。SVN的工作副本不仅仅是源代码的拷贝,更是一个完整的本地版本管理单元。`.svn`目录下的`wc.db`(Working Copy Database)文件,实际上是一个SQLite 3数据库,它维护着工作副本与远程仓库之间的映射关系。
**核心数据表及其作用:**
| 表名 | 主要功能 | 损坏影响 |
|------|---------|---------|
| `NODES` | 记录工作副本中每个文件/目录的版本、路径、类型、属性等核心元数据 | 无法识别文件状态,`svn status`命令失效 |
| `PRISTINE` | 存储文件的“原始”内容(即从仓库检出的原始版本),用于差异比较 | `svn diff`无法工作,本地修改无法与基准对比 |
| `WCROOT` | 记录工作副本根目录与仓库URL的映射关系 | 工作副本与仓库关联丢失 |
| `REPOSITORY` | 存储访问的仓库信息 | 无法执行需要仓库连接的操作 |
| `ACTUAL_NODE` | 记录文件系统的实际状态 | 本地文件状态检测异常 |
数据库损坏通常发生在**写入过程被意外中断**时。想象一下这样的场景:SVN正在更新工作副本,同时向`wc.db`写入新的版本信息,此时系统突然断电或进程被强制终止。SQLite虽然具有事务原子性保证,但在某些极端情况下,文件系统层面的损坏仍可能导致数据库头信息或页结构异常。另一种常见情况是**磁盘坏道**——即使SVN操作正常,存储介质本身的物理损坏也会悄无声息地破坏数据完整性。
> **注意**:在尝试任何修复操作之前,**必须备份原始的`.svn/wc.db`文件**。即使修复失败,你仍然可以恢复到初始状态,避免因操作失误导致数据永久丢失。备份命令很简单:
> ```bash
> cp .svn/wc.db .svn/wc.db.backup
> ```
识别损坏的早期迹象也很重要。除了明显的`malformed`错误外,以下症状也可能暗示数据库问题:
- `svn status`命令输出异常(如显示不存在的文件)
- 执行`svn update`时出现“checksum mismatch”错误
- 某些文件的状态始终显示为“modified”,即使你已还原更改
- SVN客户端操作异常缓慢,伴随大量磁盘I/O
## 2. 第一层修复:快速文件替换法(团队协作场景)
当你身处一个开发团队中,并且有同事维护着相同代码分支的工作副本时,**文件替换法**是最快捷的解决方案。这种方法的核心思想是“借用”一个健康的数据库文件,替换掉损坏的文件。它的前提是:两个工作副本指向**相同的仓库URL和修订版本**。
### 2.1 适用场景与前提条件
这种方法最适合以下情况:
- 团队中其他成员有可用的、正常的工作副本
- 你的本地修改**尚未**被损坏的数据库影响(即实际代码文件完好)
- 两个工作副本的SVN版本相同或兼容
- 你需要**立即**恢复工作,没有时间进行复杂修复
**关键检查步骤:**
1. **确认仓库一致性**
```bash
# 在正常的工作副本中执行
svn info | grep URL
# 输出示例:URL: https://svn.example.com/repos/project/trunk
# 在你的工作副本中执行(如果可能)
cd /path/to/your/broken/workspace
cat .svn/entries # 旧格式工作副本
# 或尝试读取wc.db(如果部分功能仍工作)
sqlite3 .svn/wc.db "select root from repository"
```
2. **检查修订版本号**
```bash
# 在正常副本中
svn info | grep Revision
# 输出示例:Revision: 12345
```
3. **验证本地修改状态**
```bash
# 在你的工作副本中,即使数据库损坏,也可以手动检查
# 查看哪些文件有未提交的修改
find . -name "*.java" -o -name "*.py" -o -name "*.js" | xargs grep -l "YOUR_SPECIAL_CHANGE_MARKER"
# 或使用git diff(如果你同时使用git管理本地更改)
git diff --name-only # 假设你同时使用git进行本地版本控制
```
### 2.2 详细操作流程
假设同事小张有一个正常的工作副本在`/home/zhang/project`,而你的损坏副本在`/home/you/project`:
```bash
# 1. 备份你的损坏数据库(绝对必要!)
cd /home/you/project
cp -p .svn/wc.db .svn/wc.db.bak
# 2. 获取正常数据库文件
# 方法A:直接复制(在同一台机器上)
cp /home/zhang/project/.svn/wc.db .svn/
# 方法B:如果不在同一机器,先打包传输
# 在小张的机器上:
cd /home/zhang/project
tar czf wc.db.tar.gz .svn/wc.db
# 传输到你的机器后:
tar xzf wc.db.tar.gz -C /home/you/project/.svn/
# 3. 验证替换后的状态
svn info # 应该正常显示仓库信息
svn status # 检查文件状态
```
**可能遇到的问题及解决方案:**
- **问题1**:替换后`svn status`显示大量文件为“missing”状态
**原因**:两个工作副本的文件系统路径不同,数据库中的路径记录不匹配
**解决**:需要更新数据库中的工作副本根路径记录:
```bash
sqlite3 .svn/wc.db "UPDATE WCROOT SET local_abspath = '/home/you/project' WHERE id = 1;"
```
- **问题2**:本地修改在`svn status`中不显示
**原因**:新数据库不知道你的本地修改
**解决**:这实际上是正常现象。你需要重新应用修改或使用`svn diff`对比:
```bash
# 为每个修改过的文件执行
svn diff path/to/modified/file.java > my_changes.patch
# 如果显示正常,说明修改仍在文件系统中,只是SVN不知道
```
- **问题3**:操作权限错误
**原因**:复制的文件可能保留了原用户的权限
**解决**:调整文件所有权:
```bash
chown your_username:your_group .svn/wc.db
chmod 644 .svn/wc.db
```
> **重要提示**:文件替换法虽然快速,但存在潜在风险。如果两个工作副本的SVN客户端版本差异较大(如1.8 vs 1.14),数据库格式可能不兼容。建议在执行前,先用`svn --version`确认双方版本。如果版本差异超过两个主要版本,最好先尝试其他方法。
## 3. 第二层修复:SQLite3命令修复(中度损坏)
当没有可用的健康数据库文件,或者文件替换法失败时,我们需要直接操作损坏的数据库文件。SQLite3提供了一系列维护命令,可以修复大多数软件层面的损坏。这种方法比文件替换更可靠,因为它直接处理问题根源。
### 3.1 SQLite3工具的准备与基础操作
首先确保你安装了SQLite3命令行工具:
```bash
# 在Linux/macOS上检查是否安装
which sqlite3
# 如果未安装,使用包管理器安装
# Ubuntu/Debian:
sudo apt-get install sqlite3
# macOS:
brew install sqlite3
# 在Windows上,可以从官网下载预编译二进制文件
# https://www.sqlite.org/download.html
# 下载sqlite-tools-win32-*.zip并解压,将sqlite3.exe加入PATH
```
进入损坏的工作副本的`.svn`目录:
```bash
cd /path/to/your/project/.svn
```
### 3.2 完整性检查与基础修复
**第一步:完整性检查**
```bash
# 运行完整性检查,查看损坏程度
sqlite3 wc.db "pragma integrity_check;"
```
这个命令会输出一个检查列表。理想情况下,你应该看到简单的`ok`。如果数据库有损坏,你会看到类似这样的输出:
```
row X missing from index I_NODES_PARENT
wrong # of entries in index I_NODES_PARENT
*** in database main ***
Page X: free space corruption
```
**第二步:尝试自动修复**
SQLite的`REINDEX`命令可以重建索引,解决索引损坏问题:
```bash
# 重建所有索引
sqlite3 wc.db "REINDEX;"
# 或者针对SVN特定的关键索引
sqlite3 wc.db "REINDEX NODES;"
sqlite3 wc.db "REINDEX PRISTINE;"
sqlite3 wc.db "REINDEX I_NODES_PARENT;"
```
**第三步:使用备份和恢复**
如果REINDEX不能解决问题,可以尝试导出/导入整个数据库:
```bash
# 导出数据库内容到SQL文件
sqlite3 wc.db .dump > wc_backup.sql
# 创建一个新的数据库
mv wc.db wc.db.corrupted
sqlite3 wc.db < wc_backup.sql
# 检查新数据库的完整性
sqlite3 wc.db "pragma integrity_check;"
```
### 3.3 高级修复:手动修复损坏的表
当自动修复无效时,我们需要手动干预。以下是一个完整的修复流程,针对最常见的`NODES`表损坏:
```bash
# 1. 首先备份当前状态
cp wc.db wc.db.before_manual_fix
# 2. 检查表结构
echo "=== 检查NODES表结构 ==="
sqlite3 wc.db ".schema NODES"
# 3. 如果表结构看起来正常,检查数据完整性
echo "=== 检查NODES表数据问题 ==="
sqlite3 wc.db <<EOF
-- 查找可能的重复主键
SELECT wc_id, local_relpath, op_depth, COUNT(*) as cnt
FROM NODES
GROUP BY wc_id, local_relpath, op_depth
HAVING cnt > 1;
-- 查找外键约束问题
SELECT * FROM NODES WHERE wc_id NOT IN (SELECT id FROM WCROOT);
-- 检查非空字段的空值
SELECT * FROM NODES WHERE presence IS NULL OR kind IS NULL;
EOF
# 4. 创建临时表并修复数据
sqlite3 wc.db <<EOF
-- 开启事务
BEGIN TRANSACTION;
-- 创建结构相同的临时表
CREATE TABLE NODES_TEMP AS SELECT * FROM NODES WHERE 1=0;
-- 复制数据,同时修复常见问题
INSERT INTO NODES_TEMP
SELECT
wc_id,
COALESCE(local_relpath, '') as local_relpath,
COALESCE(op_depth, 0) as op_depth,
parent_relpath,
repos_id,
repos_path,
revision,
COALESCE(presence, 'normal') as presence,
moved_here,
moved_to,
COALESCE(kind, 'file') as kind,
properties,
depth,
checksum,
symlink_target,
changed_revision,
changed_date,
changed_author,
translated_size,
last_mod_time,
dav_cache,
file_external
FROM NODES
WHERE wc_id IS NOT NULL
AND local_relpath IS NOT NULL
AND op_depth IS NOT NULL;
-- 删除原表
DROP TABLE NODES;
-- 重命名临时表
ALTER TABLE NODES_TEMP RENAME TO NODES;
-- 重新创建索引
CREATE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, op_depth);
-- 提交事务
COMMIT;
EOF
```
**修复后的验证步骤:**
```bash
# 验证修复结果
sqlite3 wc.db "pragma integrity_check;"
sqlite3 wc.db "select count(*) from NODES;"
# 测试SVN基本功能
cd ..
svn info
svn status --depth=empty # 只检查当前目录状态
```
> **专业提示**:如果数据库损坏严重,上述方法可能仍然失败。这时可以尝试使用`.dump`命令导出时添加`--preserve-rowids`选项,或者使用第三方工具如`sqlite3_recover`(需要编译)。在某些极端情况下,你可能需要手动编辑SQL转储文件,删除损坏的记录行。
## 4. 第三层修复:数据库表重构方案(终极手段)
当数据库损坏非常严重,连SQLite都无法正常读取时,我们需要更激进的方法:基于对SVN数据库结构的理解,重建关键表。这种方法风险较高,但有时是挽救数据的唯一途径。
### 4.1 分析数据库损坏程度
首先,我们需要评估损坏的严重性:
```bash
cd /path/to/your/project/.svn
# 尝试列出所有表
echo "=== 数据库中的表 ==="
sqlite3 wc.db ".tables"
# 尝试读取每个表的前几行
for table in $(sqlite3 wc.db ".tables"); do
echo "=== 表: $table ==="
sqlite3 wc.db "SELECT * FROM $table LIMIT 1;" 2>/dev/null || echo " 无法读取此表"
done
# 检查sqlite_master表(存储数据库模式)
echo "=== 数据库模式 ==="
sqlite3 wc.db "SELECT type, name, sql FROM sqlite_master ORDER BY type, name;"
```
### 4.2 关键表的重建策略
SVN工作副本数据库的核心是`NODES`和`PRISTINE`表。如果这两个表损坏,但其他表(如`WCROOT`、`REPOSITORY`)完好,我们可以尝试重建。
**重建NODES表的完整流程:**
```sql
-- 保存到文件rebuild_nodes.sql并执行:sqlite3 wc.db < rebuild_nodes.sql
-- 第一步:备份当前状态
ATTACH DATABASE 'wc_backup.db' AS backup;
CREATE TABLE backup.NODES_BACKUP AS SELECT * FROM main.NODES;
DETACH DATABASE backup;
-- 第二步:如果原表存在,获取其结构
.schema NODES
-- 第三步:基于工作副本实际文件重建NODES表
-- 这是一个简化的示例,实际需要更复杂的逻辑
BEGIN TRANSACTION;
-- 删除损坏的表(如果存在)
DROP TABLE IF EXISTS NODES_COPY;
DROP TABLE IF EXISTS NODES;
-- 创建新的NODES表(标准结构)
CREATE TABLE NODES (
wc_id INTEGER NOT NULL REFERENCES WCROOT (id),
local_relpath TEXT NOT NULL,
op_depth INTEGER NOT NULL,
parent_relpath TEXT,
repos_id INTEGER REFERENCES REPOSITORY (id),
repos_path TEXT,
revision INTEGER,
presence TEXT NOT NULL,
moved_here INTEGER,
moved_to TEXT,
kind TEXT NOT NULL,
properties BLOB,
depth TEXT,
checksum TEXT REFERENCES PRISTINE (checksum),
symlink_target TEXT,
changed_revision INTEGER,
changed_date INTEGER,
changed_author TEXT,
translated_size INTEGER,
last_mod_time INTEGER,
dav_cache BLOB,
file_external TEXT,
PRIMARY KEY (wc_id, local_relpath, op_depth)
);
-- 创建必要的索引
CREATE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, op_depth);
CREATE INDEX I_NODES_REPOS_PATH ON NODES (repos_path);
CREATE INDEX I_NODES_MOVED_TO ON NODES (moved_to);
-- 第四步:从文件系统重建基础数据
-- 这里需要根据实际情况编写,以下是一个概念性示例
-- 实际实现可能需要遍历工作副本目录树
-- 假设wc_id=1,repos_id=1(从WCROOT和REPOSITORY表获取)
INSERT INTO NODES (wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, revision, presence, kind)
VALUES
(1, '', 0, NULL, 1, '', 12345, 'normal', 'dir'),
(1, 'src', 0, '', 1, 'trunk/src', 12345, 'normal', 'dir'),
(1, 'src/main.java', 0, 'src', 1, 'trunk/src/main.java', 12345, 'normal', 'file');
-- 提交事务
COMMIT;
```
**PRISTINE表的重建更为复杂**,因为它存储了文件内容的哈希值。如果这个表损坏,你可能需要从仓库重新获取原始内容,或者从本地缓存恢复:
```sql
-- 重建PRISTINE表的简化示例
BEGIN TRANSACTION;
DROP TABLE IF EXISTS PRISTINE;
CREATE TABLE PRISTINE (
checksum TEXT NOT NULL PRIMARY KEY,
md5_checksum TEXT,
compression INTEGER NOT NULL,
size INTEGER NOT NULL,
refcount INTEGER NOT NULL
);
-- 从.svn/pristine目录恢复(如果存在)
-- .svn/pristine/目录存储了文件的原始版本
INSERT INTO PRISTINE (checksum, md5_checksum, compression, size, refcount)
SELECT
substr(name, 1, 32) as checksum,
NULL as md5_checksum,
0 as compression,
size as size,
1 as refcount
FROM (
SELECT name, size FROM (
SELECT
substr(filename, 1, 32) as name,
SUM(size) as size
FROM (
SELECT
f.filename as filename,
f.stat_size as size
FROM fsdir('.svn/pristine') f
WHERE f.filename LIKE '%.svn-base'
)
GROUP BY substr(filename, 1, 32)
)
);
COMMIT;
```
### 4.3 验证与恢复工作副本功能
完成表重建后,需要系统性地验证修复效果:
```bash
# 1. 基础完整性检查
sqlite3 wc.db "pragma foreign_key_check;"
sqlite3 wc.db "pragma integrity_check;"
# 2. 关键数据验证
echo "=== 验证关键表数据 ==="
sqlite3 wc.db <<EOF
-- 检查WCROOT表
SELECT * FROM WCROOT;
-- 检查REPOSITORY表
SELECT * FROM REPOSITORY;
-- 检查NODES表记录数
SELECT count(*) as node_count FROM NODES;
-- 检查PRISTINE表记录数
SELECT count(*) as pristine_count FROM PRISTINE;
EOF
# 3. 逐步测试SVN命令
cd ..
echo "=== 测试svn info ==="
svn info
echo "=== 测试svn status(仅当前目录)==="
svn status --depth=empty
echo "=== 测试svn status(递归)==="
svn status --depth=infinity 2>&1 | head -20
echo "=== 测试svn diff(如有修改)==="
svn diff 2>&1 | head -50
```
如果上述测试通过,你的工作副本基本恢复。如果仍有问题,可能需要更精细的调整,或者考虑下一节的版本回退技巧。
## 5. 紧急情况下的版本回退与数据挽救技巧
当所有修复尝试都失败时,我们还有最后一道防线:尽可能挽救本地修改。这不是修复数据库,而是绕过损坏的数据库,直接从文件系统中恢复你的工作成果。
### 5.1 识别和备份本地修改
即使SVN无法识别修改,你的代码文件可能仍然完好。首先,找出所有可能包含修改的文件:
```bash
# 方法1:使用文件修改时间
# 找出最近修改过的文件(假设损坏发生在今天)
find . -type f -name "*.java" -o -name "*.py" -o -name "*.js" -o -name "*.cpp" | \
xargs -I {} sh -c 'test "$(stat -c %Y "{}")" -gt $(date -d "yesterday" +%s) && echo {}'
# 方法2:使用内容对比(如果有干净的检出版本)
# 假设你在/path/to/clean_checkout有一个干净的检出
for file in $(find . -type f -name "*.java"); do
if ! diff -q "$file" "/path/to/clean_checkout/${file#./}" >/dev/null 2>&1; then
echo "Modified: $file"
fi
done
# 方法3:使用git(如果你同时使用git进行本地版本控制)
git init # 如果尚未初始化
git add .
git status --short
```
**创建安全的备份:**
```bash
# 创建修改文件的备份
BACKUP_DIR="/tmp/svn_rescue_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"
# 备份所有可能修改的文件
find . -type f \( -name "*.java" -o -name "*.py" -o -name "*.js" -o -name "*.cpp" -o -name "*.h" \) \
-exec cp --parents {} "$BACKUP_DIR" \;
# 创建差异备份
svn export --force https://your.svn.repo/trunk ./clean_export
for file in $(find . -type f -name "*.java"); do
if ! diff -q "$file" "./clean_export/${file#./}" >/dev/null 2>&1; then
cp --parents "$file" "${BACKUP_DIR}/diff/"
fi
done
```
### 5.2 使用外部工具恢复工作状态
**方法A:使用Git作为中间层**
```bash
# 1. 初始化Git仓库
cd /path/to/your/project
rm -rf .git # 确保没有旧的.git目录
git init
git config user.name "Rescue Operation"
git config user.email "rescue@example.com"
# 2. 添加所有文件
git add .
# 3. 创建初始提交
git commit -m "Rescue snapshot before SVN repair"
# 4. 现在你可以安全地删除.svn目录并重新检出
cd ..
mv project project.broken
svn checkout https://your.svn.repo/trunk project
# 5. 应用修改
cd project
for file in $(git -C ../project.broken diff --name-only HEAD~1 HEAD); do
if [ -f "../project.broken/$file" ]; then
cp "../project.broken/$file" "$file"
fi
done
# 6. 检查状态并提交
svn status
svn diff
# 如果一切正常,提交恢复的修改
```
**方法B:使用补丁文件**
```bash
# 1. 生成当前状态与干净检出的差异
svn export --force https://your.svn.repo/trunk ./clean_copy
diff -ruN clean_copy/ project/ > my_changes.patch
# 2. 重新检出
cd ..
mv project project.broken
svn checkout https://your.svn.repo/trunk project
# 3. 应用补丁
cd project
patch -p1 < ../project.broken/my_changes.patch
# 4. 处理可能的冲突
find . -name "*.rej" -o -name "*.orig" | while read f; do
echo "需要手动处理: $f"
# 手动合并冲突
done
```
### 5.3 预防措施与最佳实践
修复总是痛苦的,预防才是关键。以下是一些避免SVN工作副本损坏的建议:
**日常操作习惯:**
- **定期提交**:不要长时间在本地保留大量未提交的修改
- **使用分支**:在功能分支上工作,定期合并到主干
- **备份工作副本**:关键修改前备份`.svn`目录
- **使用版本控制友好工具**:避免在SVN管理目录中使用可能锁定文件的工具
**技术防护措施:**
```bash
# 创建定期备份脚本
#!/bin/bash
# backup_svn_wc.sh
PROJECT_DIR="/path/to/your/project"
BACKUP_ROOT="/backup/svn_wc"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# 备份数据库文件
cp -p "$PROJECT_DIR/.svn/wc.db" "$BACKUP_ROOT/wc.db.$TIMESTAMP"
# 保留最近7天的备份
find "$BACKUP_ROOT" -name "wc.db.*" -mtime +7 -delete
# 添加到cron定期执行
# crontab -e
# 0 */6 * * * /path/to/backup_svn_wc.sh
```
**监控与早期检测:**
```bash
# 定期检查工作副本健康状态
check_svn_health() {
local dir=$1
cd "$dir" || return 1
# 检查数据库完整性
if sqlite3 .svn/wc.db "pragma integrity_check;" 2>/dev/null | grep -q -v "^ok$"; then
echo "WARNING: SVN database integrity check failed in $dir"
return 1
fi
# 检查文件系统与数据库一致性
local status=$(svn status --depth=empty 2>&1)
if echo "$status" | grep -q "malformed"; then
echo "ERROR: SVN database malformed in $dir"
return 2
fi
echo "OK: SVN working copy in $dir is healthy"
return 0
}
# 在多个项目目录上运行检查
for project in /projects/*; do
if [ -d "$project/.svn" ]; then
check_svn_health "$project"
fi
done
```
## 6. SQLite3命令速查表:SVN数据库维护必备
掌握SQLite3命令是维护SVN工作副本的必备技能。以下是我在实际工作中总结的实用命令集,覆盖了从基础查询到高级维护的各个方面。
### 6.1 数据库诊断命令
**基本信息查询:**
```sql
-- 查看数据库信息
.database -- 显示附加的数据库
.tables -- 列出所有表
.schema -- 显示所有表的创建语句
.schema TABLE_NAME -- 显示特定表的创建语句
-- 查看表信息
pragma table_info('NODES'); -- 显示表结构
pragma index_list('NODES'); -- 显示表的所有索引
pragma index_info('I_NODES_PARENT'); -- 显示索引信息
-- 统计信息
select count(*) from NODES; -- 记录数
select count(distinct local_relpath) from NODES; -- 唯一路径数
```
**完整性检查:**
```sql
-- 基本完整性检查
pragma integrity_check; -- 全面检查
pragma quick_check; -- 快速检查(不验证内容)
-- 外键检查
pragma foreign_key_check; -- 检查外键约束
pragma foreign_key_list('NODES'); -- 显示表的外键
-- 页大小和编码
pragma page_size;
pragma encoding;
```
### 6.2 数据修复命令
**索引维护:**
```sql
-- 重建索引
reindex; -- 重建所有索引
reindex NODES; -- 重建特定表索引
reindex I_NODES_PARENT; -- 重建特定索引
-- 分析索引使用
analyze; -- 收集统计信息
select * from sqlite_stat1; -- 查看统计信息
```
**数据修复操作:**
```sql
-- 备份和恢复
.backup wc_backup.db -- 在线备份
.restore wc_backup.db -- 从备份恢复
-- 导出导入
.dump -- 导出整个数据库为SQL
.output backup.sql -- 重定向输出到文件
.dump
.output stdout
.read backup.sql -- 从SQL文件恢复
-- 修复损坏的表(以NODES为例)
begin transaction;
create table NODES_NEW as select * from NODES;
drop table NODES;
alter table NODES_NEW rename to NODES;
commit;
```
### 6.3 SVN特定查询
**工作副本状态查询:**
```sql
-- 查看工作副本根信息
select * from WCROOT;
-- 查看仓库信息
select * from REPOSITORY;
-- 查看文件状态
select local_relpath, presence, kind, revision
from NODES
where op_depth = 0
order by local_relpath;
-- 查找特定文件
select * from NODES
where local_relpath like '%Main.java%';
-- 检查修改的文件
select local_relpath, presence
from NODES
where presence != 'normal'
and op_depth = 0;
```
**PRISTINE表操作:**
```sql
-- 查看原始文件存储
select checksum, size, refcount
from PRISTINE
order by refcount desc
limit 10;
-- 查找未引用的原始文件
select checksum, size, refcount
from PRISTINE
where refcount = 0;
-- 清理未引用的原始文件
delete from PRISTINE where refcount = 0;
```
### 6.4 实用脚本示例
**自动修复脚本:**
```bash
#!/bin/bash
# auto_fix_svn_wc.sh
WC_DB="$1/.svn/wc.db"
if [ ! -f "$WC_DB" ]; then
echo "错误: 未找到wc.db文件"
exit 1
fi
echo "备份原始数据库..."
cp "$WC_DB" "${WC_DB}.backup.$(date +%Y%m%d_%H%M%S)"
echo "运行完整性检查..."
if sqlite3 "$WC_DB" "pragma integrity_check;" | grep -q -v "^ok$"; then
echo "发现数据库问题,尝试修复..."
# 尝试REINDEX
sqlite3 "$WC_DB" "reindex;"
# 再次检查
if sqlite3 "$WC_DB" "pragma integrity_check;" | grep -q -v "^ok$"; then
echo "REINDEX未能解决问题,尝试导出/导入..."
# 导出到临时文件
sqlite3 "$WC_DB" ".dump" > /tmp/wc_dump.sql
# 创建新数据库
mv "$WC_DB" "${WC_DB}.corrupted"
sqlite3 "$WC_DB" < /tmp/wc_dump.sql
echo "导出/导入完成"
fi
else
echo "数据库完整性检查通过"
fi
echo "修复完成"
```
**数据库监控脚本:**
```bash
#!/bin/bash
# monitor_svn_wc.sh
PROJECT_DIRS=("/projects/project1" "/projects/project2" "/home/user/workspace")
for dir in "${PROJECT_DIRS[@]}"; do
if [ -f "$dir/.svn/wc.db" ]; then
echo "检查: $dir"
# 检查文件大小
size=$(stat -c%s "$dir/.svn/wc.db")
if [ $size -lt 10240 ]; then
echo " 警告: 数据库文件过小 ($size 字节)"
fi
# 检查最后修改时间
mtime=$(stat -c%Y "$dir/.svn/wc.db")
now=$(date +%s)
age=$(( (now - mtime) / 86400 ))
if [ $age -gt 30 ]; then
echo " 注意: 数据库30天未修改"
fi
# 快速完整性检查
if ! sqlite3 "$dir/.svn/wc.db" "pragma quick_check;" >/dev/null 2>&1; then
echo " 错误: 快速检查失败"
fi
fi
done
```
这些命令和脚本构成了SVN工作副本维护的工具箱。实际使用时,根据具体情况组合应用。记住,**总是先备份再操作**,特别是在生产环境中。对于特别重要的项目,考虑定期将工作副本数据库备份到版本控制之外的安全位置。
面对SVN工作副本损坏,从最初的惊慌到逐步排查、尝试修复,再到最终恢复,整个过程考验的不仅是技术能力,更是系统化解决问题的能力。我经历过几次深夜紧急修复,最深的体会是:预防远比修复重要。建立定期备份习惯,使用自动化监控脚本,在关键操作前做好快照——这些看似繁琐的步骤,在真正遇到问题时能节省数小时甚至数天的恢复时间。
当修复确实不可避免时,按照本文提供的三层策略逐步推进:先尝试最简单的文件替换,再使用SQLite工具进行修复,最后考虑表重构。每层方法都有其适用场景和风险,选择时需要考虑时间紧迫性、数据重要性和你的技术舒适度。保存好那份SQLite3命令速查表,它会在很多意想不到的时刻派上用场。