从Oracle迁移角色、用户及权限至PolarDB PostgreSQL版(兼容Oracle)

更新时间:
复制 MD 格式

将业务从Oracle数据库迁移至PolarDB PostgreSQL版(兼容Oracle)时,需要完整地复制原有的安全体系。本文介绍两种迁移方式:推荐使用用户与权限迁移工具(polardb-schema-migration)自动完成迁。若运行环境无法同时连接Oracle和PolarDB PostgreSQL版(兼容Oracle),可使用SQL脚本方式手动迁移。

方案选择

本文提供以下两种迁移方案,请根据您的运行环境条件选择。

对比项

运行环境要求

操作效率

适用场景

方案一:(推荐)使用迁移工具

需要一台可以同时连接Oracle和PolarDB PostgreSQL版(兼容Oracle)的机器(例如ECS实例),并安装Java 1.8。

高。命令行一键执行,自动完成全部迁移动作。

绝大多数场景。推荐优先选用。

方案二:使用SQL脚本

无需网络打通。仅需在Oracle端通过SQLPlus客户端导出SQL,再将SQL文件传输到可连接PolarDB PostgreSQL版(兼容Oracle)的机器上,通过psql客户端导入即可。

中。需手动执行多个SQL脚本,流程相对繁琐。

环境因网络隔离等原因,无法提供同时连接Oracle和PolarDB PostgreSQL版(兼容Oracle)的机器。

方案一:(推荐)使用迁移工具

注意事项

  • Oracle源库要求:连接到Oracle的账号需要至少拥有CONNECT、CREATE SESSION、SELECT_CATALOG_ROLE权限,授权语句如下:

    GRANT CONNECT, CREATE SESSION, SELECT_CATALOG_ROLE TO myuser;
  • PolarDB目标库要求:

    • 需要先使用数据传输服务(DTS)完成Oracle到PolarDB的结构迁移。

    • 连接到PolarDB的账号需要是高权限账号。

  • 工具运行环境要求:

    • 迁移工具需要部署在一个能够同时连接Oracle和PolarDB的位置,例如一台ECS实例。

    • 运行环境需要安装Java运行环境,版本为Java 1.8。

工具部署

  1. 下载迁移工具JAR文件polardb-schema-migration-20260827.jar,将其上传到能够同时连接Oracle和PolarDB的机器上。

  2. 运行如下命令确认工具版本:

    java -jar polardb-schema-migration.jar --version
  3. 新建一个config.json文件,写入如下内容。其中数据库连接串和日志目录需要根据实际情况调整。后续迁移过程需要使用该配置文件。

    {
      "source": {
        "url": "jdbc:oracle:thin:@//<Oracle主机IP>:<端口>/<服务名>",
        "username": "<Oracle用户名>",
        "password": "<Oracle密码>"
      },
      "destination": {
        "url": "jdbc:polardb://<PolarDB集群连接地址>:<端口>/<数据库名>",
        "username": "<PolarDB用户名>",
        "password": "<PolarDB密码>"
      },
      "log": {
        "dir": "/path/to/log"
      }
    }

迁移步骤

创建内置对象

Oracle中存在上百个内置角色以及若干个内置用户,这些内置对象自带一些权限,例如DBA角色可读写所有表,SELECT_CATALOG_ROLE角色可查看系统目录。业务可按需将这些内置角色授予自建的角色和用户。

执行如下命令,在PolarDB中创建Oracle的内置角色和内置用户。该步骤通常是必需的,因为后续权限迁移阶段可能需要将这些内置角色授予给用户。

# JAR文件和config文件的路径需要根据实际情况修改,下同
java -jar polardb-schema-migration.jar --config=/path/to/config.json --create-polar-builtin-objects

创建非内置角色

执行如下命令,从Oracle导出非内置角色(ROLE)定义并创建到PolarDB中。

java -jar polardb-schema-migration.jar --config=/path/to/config.json --create-roles

创建非内置用户

执行如下命令,从Oracle导出用户(USER)定义并创建到PolarDB中。

java -jar polardb-schema-migration.jar --config=/path/to/config.json --create-users

由于源库存储的密码是加密的,暂时不支持迁移密码信息。所有用户迁移到PolarDB后都没有密码,需要手动修改密码:

ALTER USER myuser PASSWORD '<新密码>';

授予对象权限

执行如下命令,从Oracle导出对象权限(OBJECT PRIVILEGE)的授予记录,并在PolarDB中执行。主要是将表的增删改查权限、函数的执行权限等授予给角色和用户。

java -jar polardb-schema-migration.jar --config=/path/to/config.json --grant-object-privileges

授予角色

执行如下命令,从Oracle导出角色权限的授予记录,并在PolarDB中执行。其原理是将角色授予给用户,用户就拥有了该角色的权限。

java -jar polardb-schema-migration.jar --config=/path/to/config.json --grant-roles

SCHEMA权限授予

执行如下命令,将SCHEMA的USAGE权限开放给普通用户,与Oracle对齐。Oracle中不检查该项权限,相当于默认授予了该项权限。

java -jar polardb-schema-migration.jar --config=/path/to/config.json --grant-schema-usage

修改对象所有者

Oracle的SCHEMA和USER/OWNER是相同的概念,而在PolarDB中二者是独立的。执行如下命令,将对象的OWNER改为其所属SCHEMA的同名用户,在PolarDB中也实现SCHEMA与OWNER的统一。

java -jar polardb-schema-migration.jar --config=/path/to/config.json --change-object-owner

(可选)授予系统权限

系统权限属于Oracle的高阶用法,主要控制DDL的执行权限。在PolarDB中建议使用高权限账号进行DDL运维操作,因此通常不需要迁移系统权限。

重要

执行此命令前,需要先在PolarDB控制台的集群参数配置中开启polar_enable_system_privilege参数。

java -jar polardb-schema-migration.jar --config=/path/to/config.json --grant-system-privileges

(可选)设置默认角色

默认角色属于Oracle的高阶用法,控制用户登录时默认拥有的角色权限。PolarDB的用户登录时默认加载所拥有的所有角色的权限,与Oracle默认值一致,因此通常不需要执行该迁移操作。

重要

执行此命令前,需要先在PolarDB控制台的集群参数配置中开启polar_enable_default_role参数。

java -jar polardb-schema-migration.jar --config=/path/to/config.json --set-default-roles

(可选)创建PROFILE

PROFILE属于Oracle的高阶用法,用于控制密码复杂度校验、过期时间等。PolarDB的用户和密码通常在控制台设置,因此通常不需要迁移PROFILE。

java -jar polardb-schema-migration.jar --config=/path/to/config.json --create-profiles

方案二:使用SQL脚本

本方案先在Oracle端通过SQLPlus客户端执行PL/SQL程序,将Oracle中的角色、用户、权限信息导出到SQL脚本中;然后通过psql等客户端在PolarDB中执行该SQL脚本。本方案不要求Oracle与PolarDB之间网络打通。

注意事项

  • Oracle源库要求:需在Oracle中准备一个迁移账号,该账号至少拥有连接权限和查询系统表的权限。授权语句如下:

    CREATE USER <Oracle用户名> IDENTIFIED BY <Oracle密码>;
    GRANT CONNECT TO <Oracle用户名>;
    GRANT CREATE SESSION TO <Oracle用户名>;
    GRANT SELECT_CATALOG_ROLE TO <Oracle用户名>;
  • PolarDB目标库要求:需在PolarDB中准备一个高权限账号,在PolarDB控制台创建即可。

  • 密码迁移限制:由于Oracle中存储的密码是加密的,本方案不支持迁移密码信息,所有用户迁移到PolarDB后均无密码,需要手动重新设置。

  • Oracle版本说明:本文档中的脚本针对Oracle 12及以上版本和Oracle 11版本分别提供。

    • Oracle 12及以上版本可以使用系统视图中的ORACLE_MAINTAINED='N'列高效过滤Oracle系统用户和角色。

    • Oracle 11版本因系统视图缺少该列,需要通过显式列出常见系统对象名的方式过滤,实际环境中如有遗漏需根据实际情况动态调整过滤条件。

迁移步骤

创建内置对象

Oracle中存在上百个内置角色以及若干个内置用户,这些内置对象自带一些权限,例如DBA角色可读写所有表,SELECT_CATALOG_ROLE角色可查看系统目录。业务可按需将这些内置角色授予自建的角色和用户。

在PolarDB侧通过psql客户端(需使用高权限账号)执行如下SQL,创建Oracle的内置角色和内置用户。该步骤通常是必需的,因为后续权限迁移阶段可能需要将这些内置角色授予给用户。

-- 创建Oracle内置角色扩展(提供DBA、CONNECT、RESOURCE等内置角色)
CREATE EXTENSION IF NOT EXISTS polar_builtin_role;

-- 创建PUBLIC用户,可以作为PUBLIC SYNONYM等对象的owner
CREATE USER "PUBLIC";

-- 创建system用户
CREATE USER system;

-- 创建system模式
CREATE SCHEMA IF NOT EXISTS system;

-- 修改system模式的owner
ALTER SCHEMA system OWNER TO system;

创建非内置角色

执行如下PL/SQL脚本,从Oracle导出非内置角色(ROLE)的CREATE ROLE语句到create_role.sql文件(可通过SPOOL路径指定输出位置),将该文件传输到PolarDB侧通过psql客户端执行。

Oracle 12及以上版本

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
-- 执行结果实际存储位置,需要根据实际情况修改
SPOOL /my/path/to/create_role.sql

BEGIN
    FOR rec IN (
        SELECT 'CREATE ROLE ' || ROLE || ';' AS ddl_script
        FROM DBA_ROLES
        WHERE ORACLE_MAINTAINED = 'N'
        ORDER BY ROLE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Oracle 11版本

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/create_role.sql

BEGIN
    FOR rec IN (
        SELECT 'CREATE ROLE ' || ROLE || ';' AS ddl_script
        FROM DBA_ROLES
        WHERE ROLE NOT IN (
            'CONNECT', 'RESOURCE', 'DBA', 'SELECT_CATALOG_ROLE',
            'EXECUTE_CATALOG_ROLE', 'DELETE_CATALOG_ROLE', 'EXP_FULL_DATABASE',
            'IMP_FULL_DATABASE', 'LOGSTDBY_ADMINISTRATOR', 'RECOVERY_CATALOG_OWNER',
            'SCHEDULER_ADMIN', 'HS_ADMIN_SELECT_ROLE', 'HS_ADMIN_EXECUTE_ROLE',
            'HS_ADMIN_ROLE', 'GLOBAL_AQ_USER_ROLE', 'OEM_ADVISOR', 'OEM_MONITOR',
            'MGMT_USER', 'DATAPUMP_EXP_FULL_DATABASE', 'DATAPUMP_IMP_FULL_DATABASE',
            'ADM_PARALLEL_EXECUTE_TASK', 'GATHER_SYSTEM_STATISTICS',
            'OPTIMIZER_PROCESSING_RATE', 'ACCESS_CATALOG_ROLE', 'JAVAUSERPRIV',
            'JAVAIDPRIV', 'JAVASYSPRIV', 'JAVADEBUGPRIV', 'EJBCLIENT', 'JMXSERVER',
            'JAVA_ADMIN', 'JAVA_DEPLOY', 'AUTHENTICATEDUSER', 'XDB_SET_INVOKER',
            'XDB_WEBSERVICES', 'XDB_WEBSERVICES_WITH_PUBLIC', 'XDB_WEBSERVICES_OVER_HTTP',
            'XDBADMIN', 'XDB_WEBSERVICES_OVER_HTTPS', 'CTXAPP', 'ORDADMIN',
            'WM_ADMIN_ROLE', 'OLAP_USER', 'OLAP_DBA', 'OLAP_XS_ADMIN',
            'CSW_USR_ROLE', 'WFS_USR_ROLE', 'SPATIAL_CSW_ADMIN',
            'SPATIAL_WFS_ADMIN', 'LBAC_DBA', 'APEX_ADMINISTRATOR_ROLE',
            'FLOWS_FILES_ROLE', 'AQ_ADMINISTRATOR_ROLE', 'AQ_USER_ROLE',
            'CWM_USER', 'DBFS_ROLE', 'OLAPI_TRACE_USER', 'OWB$CLIENT',
            'OWB_DESIGNCENTER_VIEW', 'OWB_USER'
        )
        AND ROLE NOT LIKE 'APEX\_%' ESCAPE '\'
        AND ROLE NOT LIKE 'FLOWS\_%' ESCAPE '\'
        AND ROLE NOT LIKE 'ORA\_%' ESCAPE '\'
        AND ROLE NOT LIKE 'ORACLE\_%' ESCAPE '\'
        AND ROLE NOT LIKE 'SYS\_%' ESCAPE '\'
        ORDER BY ROLE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

创建非内置用户

执行如下PL/SQL脚本,从Oracle导出非内置用户(USER)的CREATE USER语句,将SQL文件传输到PolarDB侧通过psql客户端执行。

Oracle 12及以上版本

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/create_user.sql

BEGIN
    FOR rec IN (
      SELECT 'CREATE USER ' || USERNAME || ';' AS ddl_script
      FROM DBA_USERS
      WHERE ORACLE_MAINTAINED = 'N'
      ORDER BY USERNAME
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Oracle 11版本

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/create_user.sql

BEGIN
    FOR rec IN (
        SELECT 'CREATE USER ' || USERNAME || ';' AS ddl_script
        FROM DBA_USERS
        WHERE USERNAME NOT IN (
            'SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN',
            'CTXSYS', 'MDSYS', 'ORDSYS', 'ORDDATA', 'ORDPLUGINS', 'SI_INFORMTN_SCHEMA',
            'WMSYS', 'EXFSYS', 'LBACSYS', 'XDB', 'ANONYMOUS', 'XS$NULL',
            'OLAPSYS', 'MDDATA', 'OWBSYS', 'OWBSYS_AUDIT',
            'APEX_030200', 'APEX_040000', 'APEX_040100', 'APEX_040200', 'APEX_050000',
            'APEX_PUBLIC_USER', 'FLOWS_FILES', 'FLOWS_030000', 'FLOWS_040000',
            'SPATIAL_CSW_ADMIN_USR', 'SPATIAL_WFS_ADMIN_USR',
            'DIP', 'ORACLE_OCM', 'APPQOSSYS', 'MGMT_VIEW', 'SCOTT', 'HR',
            'TSMSYS', 'WKPROXY', 'WK_TEST', 'PERFSTAT', 'TRACESVR',
            'GSMADMIN_INTERNAL', 'GSMCATUSER', 'GSMUSER', 'SYSBACKUP', 'SYSDG', 'SYSKM'
        )
        AND USERNAME NOT LIKE 'APEX\_%' ESCAPE '\'
        AND USERNAME NOT LIKE 'FLOWS\_%' ESCAPE '\'
        AND USERNAME NOT LIKE 'ORA\_%' ESCAPE '\'
        AND USERNAME NOT LIKE 'ORACLE\_%' ESCAPE '\'
        AND USERNAME NOT LIKE 'SYS\_%' ESCAPE '\'
        ORDER BY USERNAME
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

由于Oracle中存储的密码是加密的,导出的CREATE USER语句中不包含密码,所有用户迁移到PolarDB后均无密码,需要手动为每个用户设置密码:

ALTER USER <用户名> WITH PASSWORD '<新密码>';

授予对象权限

执行如下PL/SQL脚本,从Oracle导出对象权限(OBJECT PRIVILEGE)的GRANT语句到grant_object.sql文件,将文件传输到PolarDB侧通过psql客户端执行,用于将表的增删改查权限、函数的执行权限等授予给角色和用户。

导出脚本使用LISTAGG将同一对象上对同一GRANTEE的多个权限合并为一条GRANT语句(例如SELECT、INSERT、UPDATE合并为GRANT SELECT, INSERT, UPDATE ON ...),并按GRANTABLE(YES/NO)分组,因为WITH GRANT OPTION只能整条生效。

说明

当某个对象上权限非常多时,LISTAGG结果可能超过4000字节导致ORA-01489错误。此时可以去掉LISTAGG,改为每个PRIVILEGE单独一条GRANT语句。

Oracle 12及以上版本

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET DEFINE OFF
SET SERVEROUTPUT ON
SPOOL /my/path/to/grant_object.sql

BEGIN
    FOR rec IN (
        SELECT
            'GRANT ' || LISTAGG(p.PRIVILEGE, ', ') WITHIN GROUP (ORDER BY p.PRIVILEGE) || ' ON ' ||
            CASE WHEN MIN(o.OBJECT_TYPE) IN ('PROCEDURE', 'FUNCTION', 'TYPE') THEN MIN(o.OBJECT_TYPE) || ' '
                 ELSE '' END ||
            '"' || p.OWNER || '"."' || p.TABLE_NAME || '" TO ' || p.GRANTEE ||
            CASE WHEN p.GRANTABLE = 'YES' THEN ' WITH GRANT OPTION' ELSE '' END ||
            ';' AS ddl_script
        FROM DBA_TAB_PRIVS p
        JOIN DBA_OBJECTS o ON p.TABLE_NAME = o.OBJECT_NAME AND o.OWNER = p.OWNER
        WHERE p.OWNER IN (SELECT USERNAME FROM DBA_USERS WHERE ORACLE_MAINTAINED = 'N')
          AND p.TABLE_NAME NOT LIKE 'ISEQ$$%'
          AND p.TABLE_NAME NOT LIKE 'AQ$%'
          AND o.OBJECT_TYPE NOT IN ('QUEUE', 'PACKAGE BODY', 'TYPE BODY')
          AND p.GRANTEE IN (
              SELECT USERNAME FROM DBA_USERS WHERE ORACLE_MAINTAINED = 'N'
              UNION
              SELECT ROLE FROM DBA_ROLES
              UNION
              SELECT 'PUBLIC' FROM DUAL
          )
        GROUP BY p.GRANTEE, p.OWNER, p.TABLE_NAME, p.GRANTABLE
        ORDER BY p.GRANTEE, p.OWNER, p.TABLE_NAME, p.GRANTABLE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Oracle 11版本

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET DEFINE OFF
SET SERVEROUTPUT ON
SPOOL /my/path/to/grant_object.sql

BEGIN
    FOR rec IN (
        SELECT
            'GRANT ' || LISTAGG(p.PRIVILEGE, ', ') WITHIN GROUP (ORDER BY p.PRIVILEGE) || ' ON ' ||
            CASE WHEN MIN(o.OBJECT_TYPE) IN ('PROCEDURE', 'FUNCTION', 'TYPE') THEN MIN(o.OBJECT_TYPE) || ' '
                 ELSE '' END ||
            '"' || p.OWNER || '"."' || p.TABLE_NAME || '" TO ' || p.GRANTEE ||
            CASE WHEN p.GRANTABLE = 'YES' THEN ' WITH GRANT OPTION' ELSE '' END ||
            ';' AS ddl_script
        FROM DBA_TAB_PRIVS p
        JOIN DBA_OBJECTS o ON p.TABLE_NAME = o.OBJECT_NAME AND o.OWNER = p.OWNER
        WHERE p.OWNER IN (
            SELECT USERNAME FROM DBA_USERS WHERE USERNAME NOT IN (
                'SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN',
                'CTXSYS', 'MDSYS', 'ORDSYS', 'ORDDATA', 'ORDPLUGINS', 'SI_INFORMTN_SCHEMA',
                'WMSYS', 'EXFSYS', 'LBACSYS', 'XDB', 'ANONYMOUS', 'XS$NULL',
                'OLAPSYS', 'MDDATA', 'OWBSYS', 'OWBSYS_AUDIT',
                'APEX_030200', 'APEX_040000', 'APEX_040100', 'APEX_040200', 'APEX_050000',
                'APEX_PUBLIC_USER', 'FLOWS_FILES', 'FLOWS_030000', 'FLOWS_040000',
                'SPATIAL_CSW_ADMIN_USR', 'SPATIAL_WFS_ADMIN_USR',
                'DIP', 'ORACLE_OCM', 'APPQOSSYS', 'MGMT_VIEW', 'SCOTT', 'HR',
                'TSMSYS', 'WKPROXY', 'WK_TEST', 'PERFSTAT', 'TRACESVR',
                'GSMADMIN_INTERNAL', 'GSMCATUSER', 'GSMUSER', 'SYSBACKUP', 'SYSDG', 'SYSKM'
            )
            AND USERNAME NOT LIKE 'APEX\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'FLOWS\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORA\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORACLE\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'SYS\_%' ESCAPE '\'
        )
          AND p.TABLE_NAME NOT LIKE 'ISEQ$$%'
          AND p.TABLE_NAME NOT LIKE 'AQ$%'
          AND o.OBJECT_TYPE NOT IN ('QUEUE', 'PACKAGE BODY', 'TYPE BODY')
          AND p.GRANTEE IN (
              SELECT USERNAME FROM DBA_USERS WHERE USERNAME NOT IN (
                  'SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN',
                  'CTXSYS', 'MDSYS', 'ORDSYS', 'ORDDATA', 'ORDPLUGINS', 'SI_INFORMTN_SCHEMA',
                  'WMSYS', 'EXFSYS', 'LBACSYS', 'XDB', 'ANONYMOUS', 'XS$NULL',
                  'OLAPSYS', 'MDDATA', 'OWBSYS', 'OWBSYS_AUDIT',
                  'APEX_030200', 'APEX_040000', 'APEX_040100', 'APEX_040200', 'APEX_050000',
                  'APEX_PUBLIC_USER', 'FLOWS_FILES', 'FLOWS_030000', 'FLOWS_040000',
                  'SPATIAL_CSW_ADMIN_USR', 'SPATIAL_WFS_ADMIN_USR',
                  'DIP', 'ORACLE_OCM', 'APPQOSSYS', 'MGMT_VIEW', 'SCOTT', 'HR',
                  'TSMSYS', 'WKPROXY', 'WK_TEST', 'PERFSTAT', 'TRACESVR',
                  'GSMADMIN_INTERNAL', 'GSMCATUSER', 'GSMUSER', 'SYSBACKUP', 'SYSDG', 'SYSKM'
              )
              AND USERNAME NOT LIKE 'APEX\_%' ESCAPE '\'
              AND USERNAME NOT LIKE 'FLOWS\_%' ESCAPE '\'
              AND USERNAME NOT LIKE 'ORA\_%' ESCAPE '\'
              AND USERNAME NOT LIKE 'ORACLE\_%' ESCAPE '\'
              AND USERNAME NOT LIKE 'SYS\_%' ESCAPE '\'
              UNION
              SELECT ROLE FROM DBA_ROLES
              UNION
              SELECT 'PUBLIC' FROM DUAL
          )
        GROUP BY p.GRANTEE, p.OWNER, p.TABLE_NAME, p.GRANTABLE
        ORDER BY p.GRANTEE, p.OWNER, p.TABLE_NAME, p.GRANTABLE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

授予角色

执行如下PL/SQL脚本,从Oracle导出角色授予关系的GRANT语句,将SQL文件传输到PolarDB侧通过psql客户端执行。将角色授予给用户后,用户即拥有该角色的权限。

Oracle 12及以上版本

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/grant_role.sql

BEGIN
    FOR rec IN (
        WITH NON_ORACLE_USERS_AND_ROLES AS (
            SELECT USERNAME AS NAME FROM DBA_USERS WHERE ORACLE_MAINTAINED = 'N'
            UNION
            SELECT ROLE AS NAME FROM DBA_ROLES WHERE ORACLE_MAINTAINED = 'N'
        ),
        FILTERED_ROLE_PRIVS AS (
            SELECT GRANTEE, GRANTED_ROLE, ADMIN_OPTION
            FROM DBA_ROLE_PRIVS
            WHERE GRANTEE IN (SELECT NAME FROM NON_ORACLE_USERS_AND_ROLES)
        )
        SELECT
            CASE
                WHEN ADMIN_OPTION = 'YES' THEN
                    'GRANT ' || GRANTED_ROLE || ' TO ' || GRANTEE || ' WITH ADMIN OPTION;'
                ELSE
                    'GRANT ' || GRANTED_ROLE || ' TO ' || GRANTEE || ';'
            END AS ddl_script
        FROM FILTERED_ROLE_PRIVS
        ORDER BY GRANTEE, GRANTED_ROLE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Oracle 11版本

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/grant_role.sql

BEGIN
    FOR rec IN (
        WITH NON_ORACLE_USERS_AND_ROLES AS (
            SELECT USERNAME AS NAME FROM DBA_USERS WHERE USERNAME NOT IN (
                'SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN',
                'CTXSYS', 'MDSYS', 'ORDSYS', 'ORDDATA', 'ORDPLUGINS', 'SI_INFORMTN_SCHEMA',
                'WMSYS', 'EXFSYS', 'LBACSYS', 'XDB', 'ANONYMOUS', 'XS$NULL',
                'OLAPSYS', 'MDDATA', 'OWBSYS', 'OWBSYS_AUDIT',
                'APEX_030200', 'APEX_040000', 'APEX_040100', 'APEX_040200', 'APEX_050000',
                'APEX_PUBLIC_USER', 'FLOWS_FILES', 'FLOWS_030000', 'FLOWS_040000',
                'SPATIAL_CSW_ADMIN_USR', 'SPATIAL_WFS_ADMIN_USR',
                'DIP', 'ORACLE_OCM', 'APPQOSSYS', 'MGMT_VIEW', 'SCOTT', 'HR',
                'TSMSYS', 'WKPROXY', 'WK_TEST', 'PERFSTAT', 'TRACESVR',
                'GSMADMIN_INTERNAL', 'GSMCATUSER', 'GSMUSER', 'SYSBACKUP', 'SYSDG', 'SYSKM'
            )
            AND USERNAME NOT LIKE 'APEX\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'FLOWS\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORA\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORACLE\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'SYS\_%' ESCAPE '\'
            UNION
            SELECT ROLE AS NAME FROM DBA_ROLES WHERE ROLE NOT IN (
                'CONNECT', 'RESOURCE', 'DBA', 'SELECT_CATALOG_ROLE',
                'EXECUTE_CATALOG_ROLE', 'DELETE_CATALOG_ROLE', 'EXP_FULL_DATABASE',
                'IMP_FULL_DATABASE', 'LOGSTDBY_ADMINISTRATOR', 'RECOVERY_CATALOG_OWNER',
                'SCHEDULER_ADMIN', 'HS_ADMIN_SELECT_ROLE', 'HS_ADMIN_EXECUTE_ROLE',
                'HS_ADMIN_ROLE', 'GLOBAL_AQ_USER_ROLE', 'OEM_ADVISOR', 'OEM_MONITOR',
                'MGMT_USER', 'DATAPUMP_EXP_FULL_DATABASE', 'DATAPUMP_IMP_FULL_DATABASE',
                'ADM_PARALLEL_EXECUTE_TASK', 'GATHER_SYSTEM_STATISTICS',
                'OPTIMIZER_PROCESSING_RATE', 'ACCESS_CATALOG_ROLE', 'JAVAUSERPRIV',
                'JAVAIDPRIV', 'JAVASYSPRIV', 'JAVADEBUGPRIV', 'EJBCLIENT', 'JMXSERVER',
                'JAVA_ADMIN', 'JAVA_DEPLOY', 'AUTHENTICATEDUSER', 'XDB_SET_INVOKER',
                'XDB_WEBSERVICES', 'XDB_WEBSERVICES_WITH_PUBLIC', 'XDB_WEBSERVICES_OVER_HTTP',
                'XDBADMIN', 'XDB_WEBSERVICES_OVER_HTTPS', 'CTXAPP', 'ORDADMIN',
                'WM_ADMIN_ROLE', 'OLAP_USER', 'OLAP_DBA', 'OLAP_XS_ADMIN',
                'CSW_USR_ROLE', 'WFS_USR_ROLE', 'SPATIAL_CSW_ADMIN',
                'SPATIAL_WFS_ADMIN', 'LBAC_DBA', 'APEX_ADMINISTRATOR_ROLE',
                'FLOWS_FILES_ROLE', 'AQ_ADMINISTRATOR_ROLE', 'AQ_USER_ROLE',
                'CWM_USER', 'DBFS_ROLE', 'OLAPI_TRACE_USER', 'OWB$CLIENT',
                'OWB_DESIGNCENTER_VIEW', 'OWB_USER'
            )
            AND ROLE NOT LIKE 'APEX\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'FLOWS\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'ORA\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'ORACLE\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'SYS\_%' ESCAPE '\'
        ),
        FILTERED_ROLE_PRIVS AS (
            SELECT GRANTEE, GRANTED_ROLE, ADMIN_OPTION
            FROM DBA_ROLE_PRIVS
            WHERE GRANTEE IN (SELECT NAME FROM NON_ORACLE_USERS_AND_ROLES)
        )
        SELECT
            CASE
                WHEN ADMIN_OPTION = 'YES' THEN
                    'GRANT ' || GRANTED_ROLE || ' TO ' || GRANTEE || ' WITH ADMIN OPTION;'
                ELSE
                    'GRANT ' || GRANTED_ROLE || ' TO ' || GRANTEE || ';'
            END AS ddl_script
        FROM FILTERED_ROLE_PRIVS
        ORDER BY GRANTEE, GRANTED_ROLE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

SCHEMA权限授予

在Oracle中,访问某个对象时,只需要校验当前用户是否拥有该对象的权限。在PolarDB中,则需要先校验当前用户是否拥有该对象所属的模式(SCHEMA)的权限,然后再校验是否拥有该对象的权限。为了与Oracle保持统一,在PolarDB中执行如下PL/pgSQL脚本,将所有用户SCHEMA的USAGE权限开放给所有用户:

DO $$
DECLARE
    schema_rec RECORD;
    sql_stmt TEXT;
BEGIN
    FOR schema_rec IN
        SELECT nspname FROM pg_namespace n
        WHERE nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
            'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
            'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND nspname NOT LIKE 'pg_temp_%' AND nspname NOT LIKE 'pg_toast_temp_%'
          AND nsppkgns = 0
        ORDER BY nspname
    LOOP
        sql_stmt := format('GRANT USAGE ON SCHEMA %I TO PUBLIC', schema_rec.nspname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
    END LOOP;
END $$;

修改对象所有者

在Oracle中,用户(USER)和模式(SCHEMA)是等价的,每个用户都默认拥有其同名模式中所有对象的权限。在PolarDB中,用户和模式则是相互独立的概念,用户不一定拥有其同名模式的权限。为了与Oracle保持统一,在PolarDB中通过psql客户端执行如下PL/pgSQL脚本(需使用高权限账号),将SCHEMA及其下所有对象的OWNER改为同名用户:

DO $$
DECLARE
    obj_rec RECORD;
    sql_stmt TEXT;
    processed INTEGER := 0;
BEGIN
    -- 1. 修改 SCHEMA 所有者
    FOR obj_rec IN
        SELECT nspname FROM pg_namespace n
        WHERE nspname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = n.nspname)
          AND nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND nspname NOT LIKE 'pg_temp_%' AND nspname NOT LIKE 'pg_toast_temp_%'
          AND nsppkgns = 0
          AND (SELECT rolname FROM pg_roles WHERE oid = n.nspowner) != n.nspname
        ORDER BY nspname
    LOOP
        sql_stmt := format('ALTER SCHEMA %I OWNER TO %I', obj_rec.nspname, obj_rec.nspname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 2. 修改 TABLE 所有者
    FOR obj_rec IN
        SELECT schemaname, tablename FROM pg_tables t
        WHERE schemaname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = t.schemaname)
          AND schemaname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND t.tableowner != t.schemaname
        ORDER BY schemaname, tablename
    LOOP
        sql_stmt := format('ALTER TABLE %I.%I OWNER TO %I', obj_rec.schemaname, obj_rec.tablename, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 2.1 修改 TABLE PARTITION 所有者
    -- ALTER TABLE 父表不会级联修改分区的 owner,需要单独修改每个分区
    FOR obj_rec IN
        SELECT n.nspname AS schemaname, c.relname AS tablename
        FROM pg_class c
        JOIN pg_namespace n ON c.relnamespace = n.oid
        JOIN pg_inherits i ON c.oid = i.inhrelid
        WHERE n.nspname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = n.nspname)
          AND n.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND c.relkind = 'r'
          AND c.relispartition = true
          AND (SELECT rolname FROM pg_roles WHERE oid = c.relowner) != n.nspname
        ORDER BY n.nspname, c.relname
    LOOP
        sql_stmt := format('ALTER TABLE %I.%I OWNER TO %I', obj_rec.schemaname, obj_rec.tablename, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 3. 修改 VIEW 所有者
    FOR obj_rec IN
        SELECT schemaname, viewname FROM pg_views v
        WHERE schemaname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = v.schemaname)
          AND schemaname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND v.viewowner != v.schemaname
        ORDER BY schemaname, viewname
    LOOP
        sql_stmt := format('ALTER VIEW %I.%I OWNER TO %I', obj_rec.schemaname, obj_rec.viewname, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 4. 修改 MATERIALIZED VIEW 所有者
    FOR obj_rec IN
        SELECT schemaname, matviewname FROM pg_matviews mv
        WHERE schemaname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = mv.schemaname)
          AND schemaname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND mv.matviewowner != mv.schemaname
        ORDER BY schemaname, matviewname
    LOOP
        sql_stmt := format('ALTER MATERIALIZED VIEW %I.%I OWNER TO %I', obj_rec.schemaname, obj_rec.matviewname, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 5. 修改 SEQUENCE 所有者
    FOR obj_rec IN
        SELECT schemaname, sequencename FROM pg_sequences s
        WHERE schemaname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s2 WHERE s2.schema_name = s.schemaname)
          AND schemaname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND s.sequenceowner != s.schemaname
        ORDER BY schemaname, sequencename
    LOOP
        sql_stmt := format('ALTER SEQUENCE %I.%I OWNER TO %I', obj_rec.schemaname, obj_rec.sequencename, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 6. 修改 FUNCTION 所有者(排除 extension 和 package 内函数)
    FOR obj_rec IN
        SELECT n.nspname AS schemaname, p.proname AS funcname,
            pg_get_function_identity_arguments(p.oid) AS args,
            CASE WHEN n.nspname = 'public' THEN 'PUBLIC' ELSE n.nspname END AS new_owner
        FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid
        WHERE EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = n.nspname)
          AND n.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats')
          AND n.nspname NOT LIKE 'pg_temp_%' AND n.nspname NOT LIKE 'pg_toast_temp_%'
          AND nsppkgns = 0 AND p.prokind = 'f'
          AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e')
          AND (SELECT rolname FROM pg_roles WHERE oid = p.proowner) !=
              CASE WHEN n.nspname = 'public' THEN 'PUBLIC' ELSE n.nspname END
        ORDER BY n.nspname, p.proname
    LOOP
        sql_stmt := format('ALTER FUNCTION %I.%I(%s) OWNER TO %I',
            obj_rec.schemaname, obj_rec.funcname, obj_rec.args, obj_rec.new_owner);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 7. 修改 PROCEDURE 所有者(排除 package 内存储过程)
    FOR obj_rec IN
        SELECT n.nspname AS schemaname, p.proname AS procname,
            pg_get_function_identity_arguments(p.oid) AS args
        FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid
        WHERE n.nspname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = n.nspname)
          AND n.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND n.nspname NOT LIKE 'pg_temp_%' AND n.nspname NOT LIKE 'pg_toast_temp_%'
          AND nsppkgns = 0 AND p.prokind = 'p'
          AND (SELECT rolname FROM pg_roles WHERE oid = p.proowner) != n.nspname
        ORDER BY n.nspname, p.proname
    LOOP
        sql_stmt := format('ALTER PROCEDURE %I.%I(%s) OWNER TO %I',
            obj_rec.schemaname, obj_rec.procname, obj_rec.args, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 8. 修改 PACKAGE 所有者
    FOR obj_rec IN
        SELECT n.nspname AS schemaname, p.pkgname AS pkgname
        FROM pg_pkg p JOIN pg_namespace n ON p.pkgnamespace = n.oid
        WHERE n.nspname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = n.nspname)
          AND n.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND (SELECT rolname FROM pg_roles WHERE oid = p.pkgowner) != n.nspname
        ORDER BY n.nspname, p.pkgname
    LOOP
        sql_stmt := format('ALTER PACKAGE %I.%I OWNER TO %I',
            obj_rec.schemaname, obj_rec.pkgname, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 9. 修改 TYPE 所有者(仅用户自定义复合类型)
    FOR obj_rec IN
        SELECT n.nspname AS schemaname, t.typname AS typename
        FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid
        WHERE n.nspname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = n.nspname)
          AND n.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND n.nsppkgns = 0
          AND t.typname NOT LIKE '\_%'
          AND t.typtype IN ('c', 'b')
          AND (t.typrelid = 0 OR EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = t.typrelid AND c.relkind = 'c'))
          AND (SELECT rolname FROM pg_roles WHERE oid = t.typowner) != n.nspname
        ORDER BY n.nspname, t.typname
    LOOP
        sql_stmt := format('ALTER TYPE %I.%I OWNER TO %I',
            obj_rec.schemaname, obj_rec.typename, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 10. 修改 SYNONYM 所有者
    FOR obj_rec IN
        SELECT n.nspname AS schemaname, s.synname AS synname,
            CASE WHEN n.nspname = 'public' THEN 'PUBLIC' ELSE n.nspname END AS new_owner
        FROM pg_synonym s JOIN pg_namespace n ON s.synnamespace = n.oid
        WHERE n.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats')
          AND n.nsppkgns = 0
          AND (SELECT rolname FROM pg_roles WHERE oid = s.owner) !=
              CASE WHEN n.nspname = 'public' THEN 'PUBLIC' ELSE n.nspname END
        ORDER BY n.nspname, s.synname
    LOOP
        sql_stmt := format('ALTER SYNONYM %I.%I OWNER TO %I',
            obj_rec.schemaname, obj_rec.synname, obj_rec.new_owner);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    RAISE NOTICE 'Total objects processed: %', processed;
END $$;

(可选)授予系统权限

系统权限属于Oracle的高阶用法,主要控制DDL的执行权限。在PolarDB中建议使用高权限账号进行DDL运维操作,因此通常不需要迁移系统权限。如有需要,可在SQLPlus中执行如下PL/SQL,通过DBA_SYS_PRIVS视图获取所有用户和角色拥有的系统权限,导出GRANT语句到grant_sys_priv.sql文件,再到PolarDB中执行。

重要
  • 查询中GRANTEE包含了所有角色(而不仅是非系统角色),因为Oracle系统角色也可能被用户手动授予一些权限,这些权限也需要迁移。同时包含PUBLIC虚拟角色(代表所有用户)。

  • 执行此命令前,需要先在PolarDB控制台的集群参数配置中开启polar_enable_system_privilege参数。

Oracle 12及以上版本

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/grant_sys_priv.sql

BEGIN
    FOR rec IN (
        SELECT
            'GRANT ' || PRIVILEGE || ' TO ' || GRANTEE ||
            CASE WHEN ADMIN_OPTION = 'YES' THEN ' WITH ADMIN OPTION' END || ';' AS ddl_script
        FROM DBA_SYS_PRIVS
        WHERE GRANTEE IN (
            SELECT USERNAME FROM DBA_USERS WHERE ORACLE_MAINTAINED = 'N'
            UNION
            SELECT ROLE FROM DBA_ROLES
            UNION
            SELECT 'PUBLIC' FROM DUAL
        )
        ORDER BY GRANTEE, PRIVILEGE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Oracle 11版本

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/grant_sys_priv.sql

BEGIN
    FOR rec IN (
        SELECT
            'GRANT ' || PRIVILEGE || ' TO ' || GRANTEE ||
            CASE WHEN ADMIN_OPTION = 'YES' THEN ' WITH ADMIN OPTION' END || ';' AS ddl_script
        FROM DBA_SYS_PRIVS
        WHERE GRANTEE IN (
            SELECT USERNAME FROM DBA_USERS WHERE USERNAME NOT IN (
                'SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN',
                'CTXSYS', 'MDSYS', 'ORDSYS', 'ORDDATA', 'ORDPLUGINS', 'SI_INFORMTN_SCHEMA',
                'WMSYS', 'EXFSYS', 'LBACSYS', 'XDB', 'ANONYMOUS', 'XS$NULL',
                'OLAPSYS', 'MDDATA', 'OWBSYS', 'OWBSYS_AUDIT',
                'APEX_030200', 'APEX_040000', 'APEX_040100', 'APEX_040200', 'APEX_050000',
                'APEX_PUBLIC_USER', 'FLOWS_FILES', 'FLOWS_030000', 'FLOWS_040000',
                'SPATIAL_CSW_ADMIN_USR', 'SPATIAL_WFS_ADMIN_USR',
                'DIP', 'ORACLE_OCM', 'APPQOSSYS', 'MGMT_VIEW', 'SCOTT', 'HR',
                'TSMSYS', 'WKPROXY', 'WK_TEST', 'PERFSTAT', 'TRACESVR',
                'GSMADMIN_INTERNAL', 'GSMCATUSER', 'GSMUSER', 'SYSBACKUP', 'SYSDG', 'SYSKM'
            )
            AND USERNAME NOT LIKE 'APEX\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'FLOWS\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORA\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORACLE\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'SYS\_%' ESCAPE '\'
            UNION
            SELECT ROLE FROM DBA_ROLES
            UNION
            SELECT 'PUBLIC' FROM DUAL
        )
        ORDER BY GRANTEE, PRIVILEGE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

(可选)设置默认角色

默认角色属于Oracle的高阶用法,控制用户登录时默认拥有的角色权限。PolarDB的用户登录时默认加载所拥有的所有角色的权限,与Oracle默认值一致,因此通常不需要执行该迁移操作。

如有需要,可在SQLPlus中执行如下PL/SQL,导出ALTER USER ... DEFAULT ROLE语句到default_role.sql文件,再到PolarDB中执行。脚本会智能判断:

  • 如果用户的默认角色数 = 0,生成ALTER USER ... DEFAULT ROLE NONE。

  • 如果用户的默认角色数 = 总角色数,生成ALTER USER ... DEFAULT ROLE ALL。

  • 否则,生成具体的角色列表。

重要

执行此命令前,需要先在PolarDB控制台的集群参数配置中开启polar_enable_default_role参数。

Oracle 12及以上版本

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/default_role.sql

BEGIN
    FOR rec IN (
        WITH user_list AS (
            SELECT ROLE AS name FROM DBA_ROLES WHERE ORACLE_MAINTAINED = 'N'
            UNION
            SELECT USERNAME AS name FROM DBA_USERS WHERE ORACLE_MAINTAINED = 'N'
        ),
        role_stats AS (
            SELECT
                GRANTEE,
                COUNT(*) AS total_roles,
                SUM(CASE WHEN DEFAULT_ROLE = 'YES' THEN 1 ELSE 0 END) AS default_roles,
                LISTAGG(CASE WHEN DEFAULT_ROLE = 'YES' THEN GRANTED_ROLE END, ', ')
                    WITHIN GROUP (ORDER BY GRANTED_ROLE) AS default_role_list
            FROM DBA_ROLE_PRIVS
            WHERE GRANTEE IN (SELECT name FROM user_list)
              AND GRANTEE NOT IN ('PDBADMIN')
            GROUP BY GRANTEE
        )
        SELECT
            'ALTER USER ' || GRANTEE || ' DEFAULT ROLE ' ||
            CASE
                WHEN default_roles = 0 THEN 'NONE'
                WHEN default_roles = total_roles THEN 'ALL'
                ELSE default_role_list
            END || ';' AS ddl_script
        FROM role_stats
        ORDER BY GRANTEE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Oracle 11版本

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/default_role.sql

BEGIN
    FOR rec IN (
        WITH user_list AS (
            SELECT ROLE AS name FROM DBA_ROLES WHERE ROLE NOT IN (
                'CONNECT', 'RESOURCE', 'DBA', 'SELECT_CATALOG_ROLE',
                'EXECUTE_CATALOG_ROLE', 'DELETE_CATALOG_ROLE', 'EXP_FULL_DATABASE',
                'IMP_FULL_DATABASE', 'LOGSTDBY_ADMINISTRATOR', 'RECOVERY_CATALOG_OWNER',
                'SCHEDULER_ADMIN', 'HS_ADMIN_SELECT_ROLE', 'HS_ADMIN_EXECUTE_ROLE',
                'HS_ADMIN_ROLE', 'GLOBAL_AQ_USER_ROLE', 'OEM_ADVISOR', 'OEM_MONITOR',
                'MGMT_USER', 'DATAPUMP_EXP_FULL_DATABASE', 'DATAPUMP_IMP_FULL_DATABASE',
                'ADM_PARALLEL_EXECUTE_TASK', 'GATHER_SYSTEM_STATISTICS',
                'OPTIMIZER_PROCESSING_RATE', 'ACCESS_CATALOG_ROLE', 'JAVAUSERPRIV',
                'JAVAIDPRIV', 'JAVASYSPRIV', 'JAVADEBUGPRIV', 'EJBCLIENT', 'JMXSERVER',
                'JAVA_ADMIN', 'JAVA_DEPLOY', 'AUTHENTICATEDUSER', 'XDB_SET_INVOKER',
                'XDB_WEBSERVICES', 'XDB_WEBSERVICES_WITH_PUBLIC', 'XDB_WEBSERVICES_OVER_HTTP',
                'XDBADMIN', 'XDB_WEBSERVICES_OVER_HTTPS', 'CTXAPP', 'ORDADMIN',
                'WM_ADMIN_ROLE', 'OLAP_USER', 'OLAP_DBA', 'OLAP_XS_ADMIN',
                'CSW_USR_ROLE', 'WFS_USR_ROLE', 'SPATIAL_CSW_ADMIN',
                'SPATIAL_WFS_ADMIN', 'LBAC_DBA', 'APEX_ADMINISTRATOR_ROLE',
                'FLOWS_FILES_ROLE', 'AQ_ADMINISTRATOR_ROLE', 'AQ_USER_ROLE',
                'CWM_USER', 'DBFS_ROLE', 'OLAPI_TRACE_USER', 'OWB$CLIENT',
                'OWB_DESIGNCENTER_VIEW', 'OWB_USER'
            )
            AND ROLE NOT LIKE 'APEX\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'FLOWS\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'ORA\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'ORACLE\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'SYS\_%' ESCAPE '\'
            UNION
            SELECT USERNAME AS name FROM DBA_USERS WHERE USERNAME NOT IN (
                'SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN',
                'CTXSYS', 'MDSYS', 'ORDSYS', 'ORDDATA', 'ORDPLUGINS', 'SI_INFORMTN_SCHEMA',
                'WMSYS', 'EXFSYS', 'LBACSYS', 'XDB', 'ANONYMOUS', 'XS$NULL',
                'OLAPSYS', 'MDDATA', 'OWBSYS', 'OWBSYS_AUDIT',
                'APEX_030200', 'APEX_040000', 'APEX_040100', 'APEX_040200', 'APEX_050000',
                'APEX_PUBLIC_USER', 'FLOWS_FILES', 'FLOWS_030000', 'FLOWS_040000',
                'SPATIAL_CSW_ADMIN_USR', 'SPATIAL_WFS_ADMIN_USR',
                'DIP', 'ORACLE_OCM', 'APPQOSSYS', 'MGMT_VIEW', 'SCOTT', 'HR',
                'TSMSYS', 'WKPROXY', 'WK_TEST', 'PERFSTAT', 'TRACESVR',
                'GSMADMIN_INTERNAL', 'GSMCATUSER', 'GSMUSER', 'SYSBACKUP', 'SYSDG', 'SYSKM'
            )
            AND USERNAME NOT LIKE 'APEX\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'FLOWS\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORA\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORACLE\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'SYS\_%' ESCAPE '\'
        ),
        role_stats AS (
            SELECT
                GRANTEE,
                COUNT(*) AS total_roles,
                SUM(CASE WHEN DEFAULT_ROLE = 'YES' THEN 1 ELSE 0 END) AS default_roles,
                LISTAGG(CASE WHEN DEFAULT_ROLE = 'YES' THEN GRANTED_ROLE END, ', ')
                    WITHIN GROUP (ORDER BY GRANTED_ROLE) AS default_role_list
            FROM DBA_ROLE_PRIVS
            WHERE GRANTEE IN (SELECT name FROM user_list)
              AND GRANTEE NOT IN ('PDBADMIN')
            GROUP BY GRANTEE
        )
        SELECT
            'ALTER USER ' || GRANTEE || ' DEFAULT ROLE ' ||
            CASE
                WHEN default_roles = 0 THEN 'NONE'
                WHEN default_roles = total_roles THEN 'ALL'
                ELSE default_role_list
            END || ';' AS ddl_script
        FROM role_stats
        ORDER BY GRANTEE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

(可选)创建PROFILE

PROFILE属于Oracle的高阶用法,用于控制密码复杂度校验、过期时间等。PolarDB的用户和密码通常在控制台设置,因此通常不需要迁移PROFILE。

如有需要,可在SQLPlus中执行如下PL/SQL获取所有用户的PROFILE,执行结果分别保存在profile_verify_functions.sql、create_profiles.sql、alter_default_profile.sql、alter_user_profile.sql 4个文件,将这些文件传输到PolarDB侧,通过psql客户端按顺序执行,即可将Oracle的PROFILE迁移到PolarDB。

说明
  • 仅迁移密码策略参数:脚本只迁移PASSWORD类参数(PASSWORD_LIFE_TIME、PASSWORD_REUSE_TIME、PASSWORD_REUSE_MAX、PASSWORD_VERIFY_FUNCTION、PASSWORD_GRACE_TIME、PASSWORD_LOCK_TIME、FAILED_LOGIN_ATTEMPTS),不迁移资源限制参数(如SESSIONS_PER_USER、CPU_PER_SESSION、CONNECT_TIME等),因为PolarDB内核目前不支持这些参数。

  • 排除系统PROFILE:ORA_CIS_PROFILE、ORA_STIG_PROFILE、MONITORING_PROFILE为Oracle内置PROFILE,不需迁移。

  • 排除内置验证函数:ORA12C_VERIFY_FUNCTION和ORA12C_STIG_VERIFY_FUNCTION是Oracle内置函数,不需导出,PolarDB已内置。

  • 执行顺序:必须按Step 1 → 2 → 3 → 4顺序执行,因为Step 2可能引用Step 1创建的验证函数。

Oracle 12及以上版本

-- ==================================================================
-- Step 1: 导出 Profile 引用的密码验证函数
-- 说明:查询 DBA_SOURCE 获取被 Profile 引用的自定义验证函数的完整定义
-- 排除 Oracle 内置的验证函数(ORA12C_VERIFY_FUNCTION, ORA12C_STIG_VERIFY_FUNCTION)
-- ==================================================================
SET SERVEROUTPUT ON SIZE UNLIMITED
SET LINESIZE 32767
SET LONG 2000000000
SET LONGCHUNKSIZE 2000000000
SET PAGESIZE 0
SET FEEDBACK OFF
SET HEADING OFF
SPOOL profile_verify_functions.sql

SELECT 'CREATE OR REPLACE ' || LISTAGG(TEXT, CHR(10)) WITHIN GROUP (ORDER BY LINE) AS ddl_script
FROM DBA_SOURCE
WHERE NAME IN (
    SELECT DISTINCT LIMIT
    FROM DBA_PROFILES
    WHERE RESOURCE_NAME = 'PASSWORD_VERIFY_FUNCTION'
      AND LIMIT IS NOT NULL
      AND LIMIT <> 'NULL'
      AND LIMIT NOT IN ('ORA12C_VERIFY_FUNCTION', 'ORA12C_STIG_VERIFY_FUNCTION')
)
AND TYPE = 'FUNCTION'
AND OWNER = 'SYS'
GROUP BY NAME, TYPE, OWNER;

SPOOL OFF

-- ==================================================================
-- Step 2: 创建非 DEFAULT 的 Profile
-- 说明:从 DBA_PROFILES 中读取密码相关参数,生成 CREATE PROFILE 语句
-- 排除系统 Profile(DEFAULT, ORA_CIS_PROFILE, ORA_STIG_PROFILE, MONITORING_PROFILE)
-- 仅迁移密码策略参数,不迁移资源限制参数
-- ==================================================================
SPOOL create_profiles.sql

SELECT DISTINCT 'CREATE PROFILE ' || PROFILE || ' LIMIT '
         || LISTAGG(RESOURCE_NAME || ' ' || LIMIT, ' ') WITHIN GROUP (ORDER BY RESOURCE_NAME)
         || ';' AS ddl_script
  FROM DBA_PROFILES WHERE RESOURCE_NAME IN (
'PASSWORD_LIFE_TIME',
'PASSWORD_REUSE_TIME',
'PASSWORD_REUSE_MAX',
'PASSWORD_VERIFY_FUNCTION',
'PASSWORD_GRACE_TIME',
'PASSWORD_LOCK_TIME',
'FAILED_LOGIN_ATTEMPTS'
) AND PROFILE NOT IN ('DEFAULT', 'ORA_CIS_PROFILE', 'ORA_STIG_PROFILE', 'MONITORING_PROFILE')
GROUP BY PROFILE;

SPOOL OFF

-- ==================================================================
-- Step 3: 修改 DEFAULT Profile
-- 说明:目标端已存在 DEFAULT profile,因此使用 ALTER PROFILE 而非 CREATE
-- ==================================================================
SPOOL alter_default_profile.sql

SELECT DISTINCT 'ALTER PROFILE DEFAULT LIMIT '
         || LISTAGG(RESOURCE_NAME || ' ' || LIMIT, ' ') WITHIN GROUP (ORDER BY RESOURCE_NAME)
         || ';' AS ddl_script
  FROM DBA_PROFILES WHERE RESOURCE_NAME IN (
'PASSWORD_LIFE_TIME',
'PASSWORD_REUSE_TIME',
'PASSWORD_REUSE_MAX',
'PASSWORD_VERIFY_FUNCTION',
'PASSWORD_GRACE_TIME',
'PASSWORD_LOCK_TIME',
'FAILED_LOGIN_ATTEMPTS'
) AND PROFILE = 'DEFAULT'
GROUP BY PROFILE;

SPOOL OFF

-- ==================================================================
-- Step 4: 绑定用户与 Profile
-- 说明:将源端非系统用户的 Profile 绑定关系迁移到目标端
-- 过滤条件:ORACLE_MAINTAINED = 'N' 排除系统用户
-- ==================================================================
SPOOL alter_user_profile.sql

SELECT 'ALTER USER ' || USERNAME || ' PROFILE ' || PROFILE || ';' AS ddl_script
FROM DBA_USERS
WHERE PROFILE NOT IN ('ORA_CIS_PROFILE', 'ORA_STIG_PROFILE', 'MONITORING_PROFILE')
  AND ORACLE_MAINTAINED = 'N'
ORDER BY USERNAME;

SPOOL OFF

Oracle 11版本

Oracle 11g 没有ORACLE_MAINTAINED列,因此 Step 4 使用预设名单过滤系统用户。Step 1、2、3 与 Oracle 12及以上版本完全相同,仅 Step 4 不同。

-- ==================================================================
-- Step 1~3: 与 Oracle 12及以上版本完全相同,不再重复
-- ==================================================================

-- ==================================================================
-- Step 4: 绑定用户与 Profile(11g 版本)
-- 说明:使用系统用户名单替代 ORACLE_MAINTAINED 列
-- ==================================================================
SPOOL alter_user_profile.sql

SELECT 'ALTER USER ' || USERNAME || ' PROFILE ' || PROFILE || ';' AS ddl_script
FROM DBA_USERS
WHERE PROFILE NOT IN ('ORA_CIS_PROFILE', 'ORA_STIG_PROFILE', 'MONITORING_PROFILE')
  AND USERNAME NOT IN (
    'SYS','SYSTEM','DBSNMP','SYSMAN','OUTLN',
    'CTXSYS','MDSYS','ORDSYS','ORDDATA','ORDPLUGINS','SI_INFORMTN_SCHEMA',
    'WMSYS','EXFSYS','LBACSYS','XDB','ANONYMOUS','XS$NULL',
    'OLAPSYS','MDDATA',
    'OWBSYS','OWBSYS_AUDIT',
    'APEX_030200','APEX_040000','APEX_040100','APEX_040200','APEX_050000',
    'APEX_PUBLIC_USER','FLOWS_FILES','FLOWS_030000','FLOWS_040000',
    'SPATIAL_CSW_ADMIN_USR','SPATIAL_WFS_ADMIN_USR',
    'DIP','ORACLE_OCM','APPQOSSYS','MGMT_VIEW','SCOTT','HR','TSMSYS',
    'WKPROXY','WK_TEST','PERFSTAT','TRACESVR',
    'GSMADMIN_INTERNAL','GSMCATUSER','GSMUSER','SYSBACKUP','SYSDG','SYSKM'
  )
  AND USERNAME NOT LIKE 'APEX\_%' ESCAPE '\'
  AND USERNAME NOT LIKE 'FLOWS\_%' ESCAPE '\'
  AND USERNAME NOT LIKE 'ORA\_%' ESCAPE '\'
  AND USERNAME NOT LIKE 'ORACLE\_%' ESCAPE '\'
  AND USERNAME NOT LIKE 'SYS\_%' ESCAPE '\'
ORDER BY USERNAME;

SPOOL OFF

后续步骤

完成角色、用户及权限迁移后,建议使用一致性校验工具比较 Oracle 源库与PolarDB目标库的用户、角色和权限是否一致。如果还通过 DTS 完成了对象结构迁移,也可以使用该工具校验表、视图、索引、序列、函数、存储过程、包等对象结构,定位差异。具体部署和报告解读方式,请参见PolarDB 结构与权限校验工具。