把Open WebUI从SQLite数据库迁移到PostgreSQL数据

把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

如果上面的操作没有报错,就说明数据库迁移完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值