把Open WebUI从SQLite数据库迁移到PostgreSQL数据
在将Open WebUI部署到生产环境时,选择合适的数据库管理系统是关键。虽然SQLite适用于小规模应用和开发阶段,但随着用户规模的增长和数据复杂性的提升,迁移到功能更强大的PostgreSQL数据库成为必然选择。PostgreSQL具备更高的并发处理能力、更丰富的功能以及更好的扩展性,能够更好地支持Open WebUI的长期发展需求。本文将详细介绍如何将Open WebUI的数据从SQLite迁移到PostgreSQL。
1、环境介绍:
OpenWebUI在Docker环境中运行,使用默认的SQLite数据库。
需要在Linux Centos8本地安装PostgreSQL数据库,之后将SQLite中的数据导入到新的数据库中。
2、安装PostgreSQL数据库
#安装yum源
yum -y install wget https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm
#禁用内置的PostgreSQL模块:
yum -qy module disable postgresql
#安装postgresql16
yum install -y postgresql16-server
初始化数据库并配置自动启动:
修改启动脚本中的PGDATA环境变量到指定的数据存储目录。
vim /lib/systemd/system/postgresql-16.service
修改下面这行
Environment=PGDATA=/data/postgres/data
初始化数据库,并生成systemctl启动配置。此时数据库数据就在指定的目录中生成了
/usr/pgsql-16/bin/postgresql-16-setup initdb
systemctl enable postgresql-16
systemctl start postgresql-16
修改数据库密码
su - postgres
#连接本地数据库
psql
#修改postgres用户的密码
ALTER USER postgres WITH PASSWORD 'xxxx';
创建webui使用的数据库
CREATE DATABASE openwebui
OWNER postgres
ENCODING 'UTF8';
GRANT ALL PRIVILEGES ON DATABASE openwebui TO postgres;
切换回root用户
exit
修改postgreSQL的配置,让远程IP可以访问
vim /data/postgres/data/pg_hba.conf
增加下面这行配置
host all all 0.0.0.0/0 md5
修改监听IP和端口
vim /data/postgres/data/postgresql.conf
listen_addresses = '*' # what IP address(es) to listen on;
# comma-separated list of addresses;
# defaults to 'localhost'; use '*' for all
# (change requires restart)
port = 5432 # (change requires restart)
max_connections = 2000 # (change requires restart)
重启数据库
systemctl restart postgresql-16
此时PostgreSQL数据库就安装并创建完成。
3、配置Open WebUI连接PostgreSQL
在正式迁移之前先使Open WebUI的db连接切换到PostgreSQL,在启动一下Open WebUI的docker,这样系统会在PostgreSQL中创建必要的数据库表,注意:此时不要注册用户,此步目的就是创建需要的表。
#docker停止一下
docker stop webui
修改环境变量
在.env文件中增加如下内容
DATABASE_URL=postgresql://postgres:xxxx@host.docker.internal:5432/openwebui
启动docker
docker start webui
待docker启动成功后,从日志中看到监听8080端口了。在到PostgreSQL中查看,可以看到数据库表都已经创创建成功了
4、编写并运行迁移脚本
使用python编写迁移脚本,注意此脚本可以在docker内执行,就不用再安装对应的python模块了。
import sqlite3
import psycopg2
from psycopg2 import sql
# 配置
SQLITE_DB_PATH = '/app/backend/data/webui.db' # SQLite 数据库路径
PG_CONFIG = {
'host': 'host.docker.internal', # PostgreSQL 主机名
'port': 5432,
'database': 'openwebui', # PostgreSQL 数据库名
'user': 'postgres', # PostgreSQL 用户名
'password': 'xxxx', # PostgreSQL 密码
}
# 辅助函数:SQLite 数据类型转 PostgreSQL 数据类型
def sqlite_to_pg_type(sqlite_type):
sqlite_type = sqlite_type.upper()
if sqlite_type == 'INTEGER':
return 'INTEGER'
elif sqlite_type == 'REAL':
return 'DOUBLE PRECISION'
elif sqlite_type == 'TEXT':
return 'TEXT'
elif sqlite_type == 'BLOB':
return 'BYTEA'
else:
return 'TEXT'
# 辅助函数:处理保留关键字和特殊字符
def get_safe_identifier(identifier):
reserved_keywords = {'user', 'group', 'order', 'table', 'select', 'where', 'from', 'index', 'constraint'}
if identifier.lower() in reserved_keywords:
# 对于保留关键字,用双引号括起来
n = identifier.replace('"', '""')
return f'"{n}"'
elif not identifier.replace('_', '').isalnum():
# 检查是否包含空格或特殊字符
n = identifier.replace('"', '""')
return f'"{n}"'
else:
return identifier
# 辅助函数:在 SQLite 中安全地引用标识符
def sqlite_escape_identifier(identifier):
# 使用双引号括起,并替换内部的双引号
n = identifier.replace('"', '""')
return f'"{n}"'
async def migrate():
# 连接 SQLite 数据库
sqlite_db = sqlite3.connect(SQLITE_DB_PATH)
sqlite_cursor = sqlite_db.cursor()
# 连接 PostgreSQL 数据库
pg_conn = psycopg2.connect(**PG_CONFIG)
pg_cursor = pg_conn.cursor()
try:
# 获取 SQLite 中的所有表名
sqlite_cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
tables = sqlite_cursor.fetchall()
for table in tables:
table_name = table[0]
# 跳过特定表格
if table_name in ("migratehistory", "alembic_version"):
print(f"跳过表格: {table_name}")
continue
safe_table_name = get_safe_identifier(table_name)
print(f"检查表格: {table_name}")
# 检查 PostgreSQL 中是否存在表格及其行数
row_count = 0
try:
pg_cursor.execute(f"SELECT COUNT(*) FROM {safe_table_name}")
row_count = pg_cursor.fetchone()[0]
except psycopg2.Error as e:
# 如果表格不存在,忽略错误
pass
if row_count > 0:
print(f"跳过表格: {table_name} 因为它已有 {row_count} 行数据")
continue
print(f"迁移表格: {table_name}")
# 获取 PostgreSQL 表格模式以确定列的数据类型
pg_column_types = {}
try:
pg_cursor.execute(
"""
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = %s
""",
[table_name]
)
pg_schema = pg_cursor.fetchall()
for col in pg_schema:
pg_column_types[col[0]] = col[1]
except psycopg2.Error as e:
print(f"PostgreSQL 中不存在表格 {table_name},将创建新表")
# 获取 SQLite 中表格的模式信息
sqlite_cursor.execute(f"PRAGMA table_info({sqlite_escape_identifier(table_name)})")
schema = sqlite_cursor.fetchall()
# 创建 PostgreSQL 表格
columns = []
for col in schema:
col_name = get_safe_identifier(col[1])
col_type = sqlite_to_pg_type(col[2])
columns.append(f"{col_name} {col_type}")
create_table_query = f"CREATE TABLE IF NOT EXISTS {safe_table_name} ({', '.join(columns)})"
pg_cursor.execute(create_table_query)
# 从 SQLite 获取表格数据
sqlite_cursor.execute(f"SELECT * FROM {sqlite_escape_identifier(table_name)}")
rows = sqlite_cursor.fetchall()
# 插入数据到 PostgreSQL
for row in rows:
columns = []
values = []
for idx, value in enumerate(row):
col_name = get_safe_identifier(schema[idx][1])
columns.append(col_name)
# 处理布尔值转换
if pg_column_types.get(col_name) == 'boolean':
values.append('TRUE' if value == 1 else 'FALSE')
elif value is None:
values.append('NULL')
elif isinstance(value, str):
n = value.replace("'", "''")
values.append(f"'{n}'")
else:
values.append(str(value))
insert_query = f"INSERT INTO {safe_table_name} ({', '.join(columns)}) VALUES ({', '.join(values)})"
pg_cursor.execute(insert_query)
print(f"已迁移 {len(rows)} 行数据从表格 {table_name}")
print("数据库迁移成功完成!")
pg_conn.commit()
except Exception as error:
print(f"迁移过程中出错: {error}")
pg_conn.rollback()
finally:
# 关闭数据库连接
sqlite_db.close()
pg_cursor.close()
pg_conn.close()
# 运行迁移
if __name__ == "__main__":
import asyncio
asyncio.run(migrate())
将上述文件放到backend目录下migrate.py,进入docker容器中,再命令行执行如下命令:
docker exec -it webui bash
python migrate.py
在数据迁移完成后,重启一下docker容器,就完成迁移工作了。
docker stop webui
docker start webui
如果上面的操作没有报错,就说明数据库迁移完成了。