数据库检查和修复
本节内容
本节包括两项内容:如何判断海豹的数据库是否损坏,在损坏时如何修复。
数据库损坏发生的原因很多,包括但不限于突发断电、硬盘受到物理损坏、或硬盘空间占满。
如果准备升级海豹,建议先把数据库修好再升级,海豹目前暂无法通过迁移与升级修复过往产生的数据库损坏。
确定问题
如果你发现角色卡或 Log 会在重启海豹后丢失,或数据库文件变得很大(通常在 200MB 以上),建议对数据库进行完整性检查。
首先,停止海豹运行。在 Windows 系统上,你可以右键点击托盘图标并选择退出;在其他系统上,通常是在执行海豹核心的终端使用 ctrl+c 组合键;如果你注册了系统服务,通常是使用 systemctl stop sealdice 命令。
稍作等待,以确保数据被写入硬盘。
拷贝数据库时必须同时处理 WAL 和 SHM 文件
如果要把数据库复制到其他目录或电脑上修复,必须同时处理与数据库同名的 -wal 和 -shm 文件。例如,复制 data.db 时,可能还需要处理 data.db-wal 和 data.db-shm。
关闭海豹后,请选择以下任一方式:
- 将
.db、.db-wal和.db-shm三个文件一起复制到修复目录; - 只复制
.db文件,但在打开它之前,先删除修复目录中同名的旧.db-wal和.db-shm文件。
同时,请注意,删除 wal 文件和 shm 文件可能会造成部分数据库的数据丢失。若无必要,建议选择第一个方案。
另外,在执行了下文的修复操作后,修复完毕的数据库往往并不包含 wal/shm 文件,这是由 SQLite 的特性决定的。我们可以认为,在修复过程中产生的 wal,shm 文件的数据内容已经被写入修复完毕的数据库中。
因此,在将修复完毕的 db 文件拷贝回海豹目录时,应当删除海豹目录下的 wal/shm 文件,防止对数据库进行干扰导致数据损坏。
随后,使用命令行程序进入海豹的目录,执行以下命令。如果对使用命令行感到困难,后面有一个简化的替代方案。
sealdice-core /db-check你将看到类似的输出
数据库检查结果:
data.db: true
data-logs.db: true
data-censor.db: true这代表数据库是正常的。列出的是海豹核心使用的 3 个数据库文件:
- data.db - 人物卡和群内临时卡
- data-logs.db - 跑团日志
- data-censor.db - 敏感词库
如果有某个数据库文件后输出了 false,说明该文件内容已损坏。
无法使用命令行
如果你对使用命令行感到困难,可以这样做:
打开记事本,将以下内容复制进去:
sealdice-core /db-check
pause将它命名为 检查.cmd,保存在海豹的主程序所在目录,或者保存完复制过去。
双击 检查.cmd 执行,之后同上。
双击后无法执行?
可能是未正确修改后缀 .cmd,对于 Windows 用户,请确认启用了文件后缀的展示。可以参考 在 Windows 中启用文件后缀名显示 。
如果仍不能执行,请检查系统环境是否存在损坏,或是否有其他软件阻止执行。
修复数据库 - 通过回滚备份
对于大多数情况,我们推荐直接回滚到备份文件的状态。这种方法简便、容易成功。代价是损失从备份时间点到当前时间的数据。
海豹核心默认每 12 小时进行一次备份,你可在 backups/ 目录下找到所有的备份文件。备份的时间可以直接查看文件创建时间,也可以从文件名中确定。
将你损坏的数据库文件另外保存一份以防万一,并且确保你的硬盘有适当的空闲空间。
在备份文件中找到最新的一份(如果你能确定导致你数据库出问题的事件,也可以找到该时间点前的最后一份),从中解压出数据库文件,替换掉你发现损坏的数据库。
替换完成后,再进行一次完整性检查。如果仍然提示损坏,则使用更早的一份备份重新替换,直到数据库文件正常。
修复数据库 - 通过 SQLite 导出和重建
如果没有可用备份,或者你想尽量从损坏库里抢回数据,可以用 SQLite 3 的 .recover 命令导出后重建。
全程需要保持海豹关闭,不要在海豹运行时复制或替换数据库。也不要直接在 data/default/ 里对原文件做 .recover,先复制出来再进行操作。
这套方法对命令行和 SQLite 版本都有一点要求。下面以 Windows 为例,其他系统会存在可执行文件名和路径不同,但原理相同,理解原理后自行替换即可。
首先,准备一个 SQLite 3 程序。
你可以从其官网下载页找到 Precompiled Binaries for Windows,下载其中的 sqlite-tools。确保你下载的是 3.40 以上版本,通常直接下载最新版即可。
把 sqlite3.exe 放到一个空目录里备用。
把损坏的数据库从海豹的 data/default/ 目录复制出来,放到和 sqlite3.exe 同一个目录。复制时要把对应的 -wal、-shm 文件一起带上;没有就跳过。
提示:
若你使用了 MySQL/PGSQL 数据库,此处的数据恢复对你仅有参考作用。
使用命令行工具打开这个目录,在此目录下,根据不同的数据库,调整对应的指令:
data.dbdata-logs.dbdata-censor.db
导出数据库
三个数据库都可以先用 .recover 导出,再把导出的 SQL 读回一个新的数据库文件,这样做可以保留原本的数据库,防止可能的、因出现意外而数据丢失的情况发生。
以下以 data.db 为例:
sqlite3.exe data.db
.output recover-data.sql
.recover
.exitsqlite3.exe fixed-data.db
.read recover-data.sql
.exit处理其他两个库时,只需要把文件名替换成 data-logs.db / data-censor.db,以及对应的 recover-*.sql / fixed-*.db。
之后,我们通过命令行打开数据库,对数据库进行修复,以打开 data.db 进行修复为例:
sqlite3.exe fixed-data.db其他数据库同样可以这样操作以打开数据库,之后,再通过下列章节的内容输入指令对数据库进行修复。
修复 data.db
data.db 会随版本变化而不同。先执行 .tables,确认你面对的是旧结构还是新结构,再决定清理语句。
旧结构:V146 / v1.4.6
如果 .tables 里还能看到 attrs_user、attrs_group、attrs_group_user,说明这是旧结构。
通过下列的命令先进行检查:
.tables
SELECT id FROM attrs_user LIMIT 20;
SELECT id FROM attrs_group LIMIT 20;
SELECT id FROM attrs_group_user LIMIT 20;
SELECT id FROM group_info LIMIT 20;
SELECT id FROM group_player_info LIMIT 20;
SELECT id FROM ban_info LIMIT 20;确认结果基本正常无误后,再通过下列命令清理恢复过程中产生的坏行:
delete from attrs_group where id is null;
delete from attrs_user where id is null;
delete from attrs_group_user where id is null;
delete from group_info where id is null;
delete from ban_info where id is null;
delete from group_player_info where id is null;
delete from group_info
where not (
(created_at is null or cast(created_at as integer) > 0)
and (updated_at is null or cast(updated_at as integer) > 0)
and data is not null
);
delete from ban_info
where data is null or data = '' or length(data) = 0;新结构:V150 / v1.5.0 及以上
如果 .tables 里已经有统一的 attrs 表,旧结构的清理语句会报错,需要先使用下列的命令进行检查,确认数据库结构是否为新结构。
.tables
SELECT id FROM attrs LIMIT 20;
SELECT id FROM group_info LIMIT 20;
SELECT id FROM ban_info LIMIT 20;
SELECT id FROM group_player_info LIMIT 20;确认结果基本正常无误后,再通过下列命令清理恢复过程中产生的坏行:
delete from attrs where id is null;
delete from group_info where id is null;
delete from ban_info where id is null;
delete from group_player_info where id is null;
delete from attrs
where data is null or data = '' or length(data) = 0;
delete from group_info
where not (
(created_at is null or cast(created_at as integer) > 0)
and (updated_at is null or cast(updated_at as integer) > 0)
and data is not null
);
delete from ban_info
where data is null or data = '' or length(data) = 0;注意:
如果 .tables 里同时能看到 attrs 和 attrs_*,那说明你的数据库处在迁移边界或者残留状态,你需要根据自己使用的海豹版本进行评估
1.如果你在注意到数据库损坏前使用的是 V1.4.6 的海豹核心,本次为升级 V1.5.0 及以上版本时,发现了数据库损坏,那么,你可以考虑备份文件后,可以在修复前执行如下命令以删除 attrs 表(这是升级 V1.5.0 的残留):
delete table attrs;2.若您已经长期使用 V1.5.0 及 V1.5.0 以上的海豹,并且没有发现数据库相关的报错,那么说明,升级遗留下来的产物已经存在,你可以考虑什么都不做,海豹会自行处理这部分的残留,或者,你也可以在修复前执行下述语句以清理残留产物:
delete table attrs_group;
delete table attrs_user;
delete table attrs_group_user;最后,无论您是否出现交界情况,均要执行:
.exit 以退出程序。
处理完后,fixed-data.db 就是修复后的 data.db。 处理完后,fixed-data.db 就是修复后的 data.db。
修复 data-logs.db
data-logs.db 是单独的日志库,主要是 logs 和 log_items,不要套用 data.db 的清理 SQL。这里也不需要按 V146 / V150 分两套方案。
先抽查:
.tables
PRAGMA table_info(logs);
PRAGMA table_info(log_items);
SELECT id FROM logs LIMIT 20;
SELECT log_id FROM log_items LIMIT 20;如果恢复后出现了 id = 0 或 log_id = 0 的坏行,可以按当前代码里已知的日志修复逻辑清理:
delete from log_items where log_id = 0;
delete from logs where id = 0;如果 logs 表里已经有 size 列,再执行一次重算:
update logs
set size = (
select count(1)
from log_items
where log_items.log_id = logs.id
and log_items.removed is null
);如果 PRAGMA table_info(logs); 看不到 size 列,就先不要执行上面的 update logs set size = ...,先保留 fixed-data-logs.db,再结合当前版本确认是否需要手工补列处理。
处理完后,fixed-data-logs.db 就是修复后的 data-logs.db。
修复 data-censor.db
data-censor.db 当前主要使用 censor_log 表,不要套用 data.db 或 data-logs.db 的清理语句。
先抽查:
.tables
PRAGMA table_info(censor_log);
SELECT id FROM censor_log LIMIT 20;
SELECT user_id, group_id, highest_level FROM censor_log LIMIT 20;目前没有像 data.db、data-logs.db 那样对 data-censor.db 定义一组通用的自动清理 SQL,所以这里不要机械套删。先抽查,如果确实能看出恢复出了明显垃圾行,再按实际内容有针对性处理。
处理完后,fixed-data-censor.db 就是修复后的 data-censor.db。
复制回去前再做一次完整性检查
建议先检查修复结果:
sqlite3.exe fixed-data.db "PRAGMA integrity_check;"
sqlite3.exe fixed-data-logs.db "PRAGMA integrity_check;"
sqlite3.exe fixed-data-censor.db "PRAGMA integrity_check;"只修其中一个库时,只检查对应那个 fixed-*.db 即可。
复制回 data/default/
保持海豹关闭,把原路径中待替换数据库对应的旧 -wal、-shm 文件删掉,再把修好的库复制回去并改回原文件名:
fixed-data.db改回data.dbfixed-data-logs.db改回data-logs.dbfixed-data-censor.db改回data-censor.db
替换完成后,再启动海豹。
一键修复脚本
如果对命令行有困难,我们提供了下面的一键脚本,复制并在本地创建对应系统的脚本后,可通过执行脚本进行数据库一键修复。
以下脚本在运行时进行的操作如下:
- 从
data/default/复制目标库及其-wal/-shm - 生成唯一的
.recover导出文件 - 恢复出唯一的修复结果库
- 自动执行当前已知的内容清理 SQL
- 跑一次
PRAGMA integrity_check;
脚本支持直接修复数据内容,但它不会自动把修复结果覆盖回海豹目录。你仍然应该先检查生成的 fixed-*.db,确认无误后再手工替换。
其中:
data.db会自动识别旧结构attrs_user/attrs_group/attrs_group_user和新结构attrs,并选择对应的清理 SQL。data-logs.db会自动清理id = 0/log_id = 0的坏行;如果存在size列,还会自动重算logs.size。data-censor.db当前没有通用的自动内容清理规则,所以脚本只做恢复和完整性检查。
Bash (适用于 Linux/Mac 系统)
#!/usr/bin/env bash
set -euo pipefail
log() {
printf '%s\n' "$1"
}
if [ "$#" -ne 2 ]; then
echo "Usage: $0 /path/to/data/default data.db|data-logs.db|data-censor.db"
exit 1
fi
SRC_DIR="$1"
DB_NAME="$2"
log "[STEP 1/7] Validating arguments and tool path"
case "$DB_NAME" in
data.db)
SQL_OUT="recover-data.sql"
FIXED_DB="fixed-data.db"
;;
data-logs.db)
SQL_OUT="recover-data-logs.sql"
FIXED_DB="fixed-data-logs.db"
;;
data-censor.db)
SQL_OUT="recover-data-censor.sql"
FIXED_DB="fixed-data-censor.db"
;;
*)
echo "Unsupported database: $DB_NAME"
exit 1
;;
esac
BASE_DIR="$(pwd)"
if [ -f "$BASE_DIR/sqlite3.exe" ]; then
SQLITE="$BASE_DIR/sqlite3.exe"
else
SQLITE="$(command -v sqlite3 || true)"
fi
if [ -z "$SQLITE" ]; then
echo "sqlite3 was not found in the current directory or PATH"
exit 1
fi
if [ ! -f "$SRC_DIR/$DB_NAME" ]; then
echo "Source database not found: $SRC_DIR/$DB_NAME"
exit 1
fi
log "[STEP 2/7] Preparing workspace"
WORK_DIR="$BASE_DIR/recover-${DB_NAME%.db}"
mkdir -p "$WORK_DIR"
log "[STEP 3/7] Copying database and sidecar files"
cp "$SRC_DIR/$DB_NAME" "$WORK_DIR/$DB_NAME"
for sidecar in "-wal" "-shm"; do
if [ -f "$SRC_DIR/$DB_NAME$sidecar" ]; then
cp "$SRC_DIR/$DB_NAME$sidecar" "$WORK_DIR/$DB_NAME$sidecar"
log "[INFO] Copied $DB_NAME$sidecar"
else
log "[INFO] Sidecar not found, skipped: $DB_NAME$sidecar"
fi
done
cd "$WORK_DIR"
log "[STEP 4/7] Exporting recover SQL"
"$SQLITE" "$DB_NAME" <<EOF
.output $SQL_OUT
.recover
.exit
EOF
log "[STEP 5/7] Rebuilding recovered database"
"$SQLITE" "$FIXED_DB" <<EOF
.read $SQL_OUT
.exit
EOF
cleanup_data_db() {
local schema
schema="$("$SQLITE" "$FIXED_DB" "SELECT CASE WHEN EXISTS(SELECT 1 FROM sqlite_master WHERE type='table' AND name='attrs') THEN 'v150+' WHEN EXISTS(SELECT 1 FROM sqlite_master WHERE type='table' AND name='attrs_user') THEN 'v146' ELSE 'unknown' END;")"
log "[INFO] data.db schema detected: $schema"
case "$schema" in
v146)
"$SQLITE" "$FIXED_DB" <<'EOF'
delete from attrs_group where id is null;
delete from attrs_user where id is null;
delete from attrs_group_user where id is null;
delete from group_info where id is null;
delete from ban_info where id is null;
delete from group_player_info where id is null;
delete from group_info
where not (
(created_at is null or cast(created_at as integer) > 0)
and (updated_at is null or cast(updated_at as integer) > 0)
and data is not null
);
delete from ban_info
where data is null or data = '' or length(data) = 0;
EOF
log "[INFO] Applied V146 data.db cleanup SQL"
;;
v150+)
"$SQLITE" "$FIXED_DB" <<'EOF'
delete from attrs where id is null;
delete from group_info where id is null;
delete from ban_info where id is null;
delete from group_player_info where id is null;
delete from attrs
where data is null or data = '' or length(data) = 0;
delete from group_info
where not (
(created_at is null or cast(created_at as integer) > 0)
and (updated_at is null or cast(updated_at as integer) > 0)
and data is not null
);
delete from ban_info
where data is null or data = '' or length(data) = 0;
EOF
log "[INFO] Applied V150+ data.db cleanup SQL"
;;
*)
log "[WARN] Unknown data.db schema. Automatic content cleanup skipped."
;;
esac
}
cleanup_logs_db() {
local has_size
"$SQLITE" "$FIXED_DB" <<'EOF'
delete from log_items where log_id = 0;
delete from logs where id = 0;
EOF
log "[INFO] Applied log_id=0 / id=0 cleanup SQL"
has_size="$("$SQLITE" "$FIXED_DB" "SELECT CASE WHEN EXISTS(SELECT 1 FROM pragma_table_info('logs') WHERE name = 'size') THEN 1 ELSE 0 END;")"
if [ "$has_size" = "1" ]; then
"$SQLITE" "$FIXED_DB" <<'EOF'
update logs
set size = (
select count(1)
from log_items
where log_items.log_id = logs.id
and log_items.removed is null
);
EOF
log "[INFO] Recalculated logs.size"
else
log "[WARN] logs.size column not found. Skipped size recalculation."
fi
}
cleanup_censor_db() {
log "[INFO] No generic content cleanup is defined for data-censor.db"
}
log "[STEP 6/7] Applying automatic content cleanup"
case "$DB_NAME" in
data.db)
cleanup_data_db
;;
data-logs.db)
cleanup_logs_db
;;
data-censor.db)
cleanup_censor_db
;;
esac
log "[STEP 7/7] Running integrity check"
"$SQLITE" "$FIXED_DB" "PRAGMA integrity_check;"
log "[DONE] Generated files:"
log " $WORK_DIR/$SQL_OUT"
log " $WORK_DIR/$FIXED_DB"Batch (适用于 Windows 系统)
@echo off
setlocal enabledelayedexpansion
if "%~2"=="" (
echo Usage: %~nx0 ^<data-default-dir^> ^<data.db^|data-logs.db^|data-censor.db^>
exit /b 1
)
set "SRC_DIR=%~1"
set "DB_NAME=%~2"
echo [STEP 1/7] Validating arguments and tool path
if /I "%DB_NAME%"=="data.db" (
set "SQL_OUT=recover-data.sql"
set "FIXED_DB=fixed-data.db"
) else if /I "%DB_NAME%"=="data-logs.db" (
set "SQL_OUT=recover-data-logs.sql"
set "FIXED_DB=fixed-data-logs.db"
) else if /I "%DB_NAME%"=="data-censor.db" (
set "SQL_OUT=recover-data-censor.sql"
set "FIXED_DB=fixed-data-censor.db"
) else (
echo Unsupported database: %DB_NAME%
exit /b 1
)
set "SQLITE=%~dp0sqlite3.exe"
if not exist "%SQLITE%" (
echo sqlite3.exe was not found in the current directory
exit /b 1
)
if not exist "%SRC_DIR%\%DB_NAME%" (
echo Source database not found: %SRC_DIR%\%DB_NAME%
exit /b 1
)
echo [STEP 2/7] Preparing workspace
set "WORK_DIR=%~dp0recover-%DB_NAME:.db=%"
if not exist "%WORK_DIR%" mkdir "%WORK_DIR%"
echo [STEP 3/7] Copying database and sidecar files
copy /y "%SRC_DIR%\%DB_NAME%" "%WORK_DIR%\%DB_NAME%" >nul || exit /b 1
if exist "%SRC_DIR%\%DB_NAME%-wal" (
copy /y "%SRC_DIR%\%DB_NAME%-wal" "%WORK_DIR%\%DB_NAME%-wal" >nul
echo [INFO] Copied %DB_NAME%-wal
) else (
echo [INFO] Sidecar not found, skipped: %DB_NAME%-wal
)
if exist "%SRC_DIR%\%DB_NAME%-shm" (
copy /y "%SRC_DIR%\%DB_NAME%-shm" "%WORK_DIR%\%DB_NAME%-shm" >nul
echo [INFO] Copied %DB_NAME%-shm
) else (
echo [INFO] Sidecar not found, skipped: %DB_NAME%-shm
)
pushd "%WORK_DIR%"
echo [STEP 4/7] Exporting recover SQL
(
echo .output %SQL_OUT%
echo .recover
echo .exit
) > recover-commands.txt
"%SQLITE%" "%DB_NAME%" < recover-commands.txt
echo [STEP 5/7] Rebuilding recovered database
(
echo .read %SQL_OUT%
echo .exit
) > import-commands.txt
"%SQLITE%" "%FIXED_DB%" < import-commands.txt
echo [STEP 6/7] Applying automatic content cleanup
if /I "%DB_NAME%"=="data.db" (
for /f "usebackq delims=" %%i in (`"%SQLITE%" "%FIXED_DB%" "SELECT CASE WHEN EXISTS(SELECT 1 FROM sqlite_master WHERE type='table' AND name='attrs') THEN 'v150+' WHEN EXISTS(SELECT 1 FROM sqlite_master WHERE type='table' AND name='attrs_user') THEN 'v146' ELSE 'unknown' END;"`) do set "DATA_SCHEMA=%%i"
echo [INFO] data.db schema detected: !DATA_SCHEMA!
if /I "!DATA_SCHEMA!"=="v146" (
(
echo delete from attrs_group where id is null;
echo delete from attrs_user where id is null;
echo delete from attrs_group_user where id is null;
echo delete from group_info where id is null;
echo delete from ban_info where id is null;
echo delete from group_player_info where id is null;
echo delete from group_info
echo where not ^(
echo ^(created_at is null or cast^(created_at as integer^) ^> 0^)
echo and ^(updated_at is null or cast^(updated_at as integer^) ^> 0^)
echo and data is not null
echo ^);
echo delete from ban_info
echo where data is null or data = '' or length^(data^) = 0;
) > cleanup.sql
"%SQLITE%" "%FIXED_DB%" < cleanup.sql
echo [INFO] Applied V146 data.db cleanup SQL
) else if /I "!DATA_SCHEMA!"=="v150+" (
(
echo delete from attrs where id is null;
echo delete from group_info where id is null;
echo delete from ban_info where id is null;
echo delete from group_player_info where id is null;
echo delete from attrs
echo where data is null or data = '' or length^(data^) = 0;
echo delete from group_info
echo where not ^(
echo ^(created_at is null or cast^(created_at as integer^) ^> 0^)
echo and ^(updated_at is null or cast^(updated_at as integer^) ^> 0^)
echo and data is not null
echo ^);
echo delete from ban_info
echo where data is null or data = '' or length^(data^) = 0;
) > cleanup.sql
"%SQLITE%" "%FIXED_DB%" < cleanup.sql
echo [INFO] Applied V150+ data.db cleanup SQL
) else (
echo [WARN] Unknown data.db schema. Automatic content cleanup skipped.
)
) else if /I "%DB_NAME%"=="data-logs.db" (
(
echo delete from log_items where log_id = 0;
echo delete from logs where id = 0;
) > cleanup.sql
"%SQLITE%" "%FIXED_DB%" < cleanup.sql
echo [INFO] Applied log_id=0 / id=0 cleanup SQL
for /f "usebackq delims=" %%i in (`"%SQLITE%" "%FIXED_DB%" "SELECT CASE WHEN EXISTS(SELECT 1 FROM pragma_table_info('logs') WHERE name = 'size') THEN 1 ELSE 0 END;"`) do set "HAS_SIZE=%%i"
if "!HAS_SIZE!"=="1" (
(
echo update logs
echo set size = ^(
echo select count^(1^)
echo from log_items
echo where log_items.log_id = logs.id
echo and log_items.removed is null
echo ^);
) > recalc-size.sql
"%SQLITE%" "%FIXED_DB%" < recalc-size.sql
echo [INFO] Recalculated logs.size
) else (
echo [WARN] logs.size column not found. Skipped size recalculation.
)
) else if /I "%DB_NAME%"=="data-censor.db" (
echo [INFO] No generic content cleanup is defined for data-censor.db
)
echo [STEP 7/7] Running integrity check
"%SQLITE%" "%FIXED_DB%" "PRAGMA integrity_check;"
echo [DONE] Generated files:
echo %WORK_DIR%\%SQL_OUT%
echo %WORK_DIR%\%FIXED_DB%
popd
endlocal数据库升级记录(高级用户)
如果你意识到了自己可能需要手动对数据库的升级记录进行修改,那么,下面将会帮助你如何自行手动修改数据库内的升级记录。
仅供了解数据库结构的高级用户使用
手动修改升级记录会让海豹跳过本应执行的数据库迁移,或重复执行已经完成的迁移。操作前必须关闭海豹并备份整个 data/default/ 目录。只有在确认数据库实际结构和数据状态后,才能执行下面的 SQL。
在 v1.5.1之后,当你默认使用 SQLite 时,数据库的升级记录保存了在 data/default/data.db 的 upgrade_records 表中。在此之前所使用的 upgrade_metadata.json 已不再作为升级状态来源。因此,如果你想要通过手动修改升级记录以跳过/重复执行数据库迁移操作,你应当修改 data.db 的 upgrade_records 表。
upgrade_records 表包含以下字段:
| 字段 | 含义 |
|---|---|
id | 升级项目的唯一 ID,也是判断是否已执行的依据 |
timestamp | 执行或写入记录的时间 |
success | 执行结果,1 为成功,0 为失败 |
message | 结果或错误信息 |
logs | 此升级项目产生的日志,保存为 JSON 数组 |
海豹启动时会按 id 顺序检查升级项目。只要 upgrade_records 中已经存在对应的 id,该项目就会被视为已应用并跳过;未找到 id 时,海豹会执行升级,并在执行结束后写入成功或失败记录。
V150 数据库结构升级对应两个记录:
| ID | 作用 |
|---|---|
006_V150UpgradeAttrsMigration | 将旧的三个 attrs_* 表合并为 attrs,并升级相关表结构 |
007_V150FixGroupInfoMigration | 清理 V146 遗留的异常 group_info 数据 |
可以先通过命令查看当前记录:
SELECT id, timestamp, success, message
FROM upgrade_records
ORDER BY id;手动跳过升级
仅当迁移实际上已经完成、数据库中已经存在正确的 V150 表结构,但升级记录丢失时,才可以手动补写记录。以下命令会把两个 V150 升级项目标记为已应用:
INSERT OR REPLACE INTO upgrade_records
(id, timestamp, success, message, logs)
VALUES
('006_V150UpgradeAttrsMigration', strftime('%Y-%m-%dT%H:%M:%SZ', 'now'), 1, '手动标记为已应用', '[]'),
('007_V150FixGroupInfoMigration', strftime('%Y-%m-%dT%H:%M:%SZ', 'now'), 1, '手动标记为已应用', '[]');非 V150 结构的数据库不可执行上述命令
如果数据库仍是前文所说的 V146 结构,手动添加这些记录会跳过必要的数据库迁移,导致人物卡、群组属性等数据无法正常使用。
因此,非 V150 结构的数据库最好不要通过手动添加记录以跳过升级迁移。
重置升级
如果出现了升级失败、亦或是要让海豹在下次启动时重新尝试某个升级项目的情况,则应删除对应的记录重置两个 V150 升级项目的示例命令如下,执行后重启海豹,海豹会重新进行 V150 的对应升级项目:
DELETE FROM upgrade_records
WHERE id IN (
'006_V150UpgradeAttrsMigration',
'007_V150FixGroupInfoMigration'
);删除后,应该先退出 SQLite,再启动海豹。
注意:
重新执行迁移可能再次修改表结构或数据,因此,只应在确认上次迁移未完成、或需要在备份副本上重新验证迁移时使用重置升级功能。