文档

全量备份数据上云(SQL Server 2008 R2云盘、2012及以上版本)

更新时间:

RDS SQL Server提供了将本地SQL Server数据库迁移到阿里云RDS SQL Server的数据库上云方案。您只需将本地SQL Server数据库的全量备份数据上传至阿里云的对象存储服务(OSS),然后通过RDS控制台即可将全量备份数据上云至指定RDS SQL Server数据库中。适用于数据备份、迁移和灾备恢复等场景。

说明

前提条件

  • RDS SQL Server实例版本为2008 R2云盘、2012及以上,且实例中没有与待上云数据库名称相同的数据库。如需创建实例,请参见创建RDS SQL Server实例

    说明

    RDS SQL Server 2008 R2云盘实例已停止新售,详情请参见【停售/下线】2023年07月14日起RDS SQL Server 2008 R2云盘实例停止售卖

  • RDS SQL Server实例拥有足够的存储空间。如果空间不足,请提前升级实例空间。具体操作,请参见变更配置

  • RDS SQL Server实例已创建高权限账号。具体操作,请参见创建数据库和账号

  • 在本地数据库环境中执行DBCC CHECKDB语句,以确保数据库中没有任何的allocation errorsconsistency errors。正常执行结果如下:

    ...
    CHECKDB found 0 allocation errors and 0 consistency errors in database 'xxx'.
    DBCC execution completed. If DBCC printed error messages, contact your system administrator.
  • 已开通OSS服务。具体操作,请参见开通OSS服务

  • 如果通过RAM用户登录,则必须满足以下条件:

    • RAM账号具备AliyunOSSFullAccess权限和AliyunRDSFullAccess权限。如何为RAM用户授权,请参见通过RAM对OSS进行权限管理通过RAM对RDS进行权限管理

    • 阿里云账号(主账号)已授权RDS官方服务账号可以访问您OSS的权限。

    • 所在阿里云账号(主账号)需手动创建权限策略,然后将权限添加到RAM账号中。如何创建权限策略,请参见通过脚本编辑模式创建自定义权限策略

      策略内容如下:

      {
          "Version": "1",
          "Statement": [
              {
                  "Action": [
                      "ram:GetRole"
                  ],
                  "Resource": "acs:ram:*:*:role/AliyunRDSImportRole",
                  "Effect": "Allow"
              }
          ]
      }

注意事项

  • 本方案迁移的级别为数据库,即每次只能迁移一个数据库上云。如果需要迁移多个或所有数据库,建议采用实例级的迁移上云方案,具体操作,请参见SQL Server实例级别迁移上云

  • 不支持高版本的备份文件迁移至低版本。例如从SQL Server 2016迁移到SQL Server 2012。

  • 不支持差异备份文件或日志备份文件。

  • 全量备份文件名不能包含特殊字符!@#$%^&*()_+-=,否则会导致上云失败。

  • 授予RDS服务账号访问OSS的权限以后,系统会在访问控制RAM的角色管理中创建名为AliyunRDSImportRole的角色,请勿修改或删除这个角色,否则会导致上云任务因无法下载备份文件而失败。如果修改或删除了这个角色,您需要通过数据上云向导重新授权。

  • 本方案迁移上云后,无法使用原有的数据库账号,需要在RDS控制台重新创建账号。

  • 在OSS备份数据恢复上云任务没有完成之前,请不要删除OSS上的备份文件,否则会导致上云任务失败。

  • 备份文件的后缀名必须为bak、diff、trn或log,说明如下:

    • bak:全量备份文件。

    • diff:差异备份文件。

    • trn或者log:事务日志备份文件。

    说明
    • 如果备份文件不是上述提到的文件后缀,系统可能无法正确识别该文件的类型并影响后续操作。

    • 如果您使用下载的RDS SQL Server全量备份文件,该文件默认为zip格式,请解压获取后缀名为bak的备份文件后,再进行上云操作。

费用说明

本方案中仅会产生OSS的相关费用,详情如下图所示。

image

场景

费用说明

将本地数据备份文件上传至OSS

不产生费用。

备份文件存储在OSS

会产生OSS的存储费用,计费详情请参见OSS定价

将备份文件从OSS迁移至RDS

  • 通过内网迁移至RDS,不产生费用。

  • 通过外网迁移至RDS,OSS会产生外网流出流量的费用,计费详情请参见OSS定价

步骤一:备份本地数据库

说明

在对本地数据库做全量备份之前,请确保已停止写入数据。备份过程中新写入的数据将不会被备份。

  1. 下载备份脚本,用SSMS(SQL Server Management Studio)打开备份脚本。

  2. 修改的如下参数。

    配置项

    说明

    @backup_databases_list

    需要备份的数据库,多个数据库以分号(;)或者半角逗号(,)分隔。

    @backup_type

    备份类型。参数值如下:

    • FULL:全量备份。

    • DIFF:差异备份。

    • LOG:日志备份。

    @backup_folder

    备份文件所在的本地目录。如不存在,会自动创建。

    @is_run

    是否执行备份。参数值如下:

    • 1:执行备份。

    • 0:只做检查,不执行备份。

    说明

    修改脚本中SELECT语句中的如上参数,位于脚本中YOU HAVE TO INIT PUBLIC VARIABLES HERE下。

  3. 执行备份脚本。

步骤二:上传备份文件到OSS

重要

如果OSS中已经创建了Bucket,请检查Bucket是否满足如下要求:

  • 请确保存储备份文件的OSS Bucket存储类型为标准存储。不能是低频访问存储、归档存储、冷归档存储、深度冷归档存储。更多详情,请参见存储类型概述

  • 请确保Bucket未开启数据加密。更多详情,请参见数据加密

  1. 创建存储空间Bucket。

    1. 登录OSS管理控制台

    2. 单击Bucket列表,然后单击创建Bucket

    3. 配置如下关键参数,其他参数可以保持默认。

      重要
      • 创建的存储空间仅用于本次数据上云,且上云后不再使用,因此只需配置关键参数即可,为避免数据泄露及产生相关费用,上云完成后请及时删除。

      • 创建Bucket时请勿开启数据加密。更多详情,请参见数据加密

      参数

      说明

      取值示例

      Bucket 名称

      存储空间名称,全局唯一,设置后无法修改。

      命名规则:

      • 只能包括小写字母、数字和短划线(-)。

      • 必须以小写字母或者数字开头和结尾。

      • 长度必须在3~63字符之间。

      migratetest

      地域

      Bucket所属的地域,如果您通过ECS内网上传数据至Bucket中,且通过内网将数据恢复至RDS中,则需要三者地域保持一致。

      华东1(杭州)

      存储类型

      选择标准存储。本文上云操作不支持其他存储类型的Bucket。

      标准存储

  2. 上传备份文件到OSS。

    说明

    当RDS实例和OSS的Bucket在同一地域时,二者可以通过内网互通,且数据上传速度更快,并且不会产生外网流量费用。因此,在上传备份文件时,建议将文件上传至与目标RDS实例在同一地域的Bucket上。

    本地数据库备份完成后,需要将备份文件上传到您的OSS Bucket中,您可以采用如下方法之一:

    使用ossbrowser工具上传(推荐)

    1. 下载ossbrowser

    2. 以Windows x64操作系统为例,解压下载的oss-browser-win32-x64.zip压缩包,双击运行oss-browser.exe应用程序。

    3. 使用AK登录方式,配置参数AccessKeyIdAccessKeySecret,其他参数保持默认,然后单击登入登录ossbrowser

      说明

      AccessKey用于身份验证,确保数据安全,请妥善保管,如何创建及获取,请参见创建AccessKey

    4. 单击目标Bucket,进入存储空间。进入bucket中

    5. 单击上传图标,选择需要上传的备份文件,然后单击打开,即可将本地文件上传至OSS中。

    使用OSS控制台上传

    说明

    如果备份文件小于5 GB,建议您直接通过OSS控制台上传备份文件。

    1. 登录OSS管理控制台

    2. 单击Bucket列表,然后单击目标Bucket名称。网页进入bucket

    3. 文件列表中,单击上传文件网页上传文件

    4. 您可以将备份文件拖拽至待上传文件区域,也可以单击扫描文件,选择需要上传的备份文件。网页扫描文件

    5. 单击页面下方的上传文件,即可将本地备份文件上传至OSS中。

    使用OSS API分片上传

    说明

    如果备份文件大于5 GB,建议您调用OSS API采用分片上传的方式将备份文件上传到OSS Bucket中。

    本示例以Java项目为例,从环境变量中获取访问凭证代码。运行本代码示例之前,请先配置环境变量。如何配置访问凭证,请参见配置访问凭证。更多示例,请参见分片上传

    import com.aliyun.oss.ClientException;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.common.auth.*;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.OSSException;
    import com.aliyun.oss.internal.Mimetypes;
    import com.aliyun.oss.model.*;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.List;
    
    public class Demo {
    
        public static void main(String[] args) throws Exception {
            // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            // 填写Bucket名称,例如examplebucket。
            String bucketName = "examplebucket";
            // 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
            String objectName = "exampledir/exampleobject.txt";
            // 待上传本地文件路径。
            String filePath = "D:\\localpath\\examplefile.txt";
    
            // 创建OSSClient实例。
            OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
            try {
                // 创建InitiateMultipartUploadRequest对象。
                InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectName);
    
                // 如果需要在初始化分片时设置请求头,请参考以下示例代码。
                 ObjectMetadata metadata = new ObjectMetadata();
                // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
                // 指定该Object的网页缓存行为。
                // metadata.setCacheControl("no-cache");
                // 指定该Object被下载时的名称。
                // metadata.setContentDisposition("attachment;filename=oss_MultipartUpload.txt");
                // 指定该Object的内容编码格式。
                // metadata.setContentEncoding(OSSConstants.DEFAULT_CHARSET_NAME);
                // 指定初始化分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object。
                // metadata.setHeader("x-oss-forbid-overwrite", "true");
                // 指定上传该Object的每个part时使用的服务器端加密方式。
                // metadata.setHeader(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION, ObjectMetadata.KMS_SERVER_SIDE_ENCRYPTION);
                // 指定Object的加密算法。如果未指定此选项,表明Object使用AES256加密算法。
                // metadata.setHeader(OSSHeaders.OSS_SERVER_SIDE_DATA_ENCRYPTION, ObjectMetadata.KMS_SERVER_SIDE_ENCRYPTION);
                // 指定KMS托管的用户主密钥。
                // metadata.setHeader(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION_KEY_ID, "9468da86-3509-4f8d-a61e-6eab1eac****");
                // 指定Object的存储类型。
                // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard);
                // 指定Object的对象标签,可同时设置多个标签。
                // metadata.setHeader(OSSHeaders.OSS_TAGGING, "a:1");
                // request.setObjectMetadata(metadata);
    
                // 根据文件自动设置ContentType。如果不设置,ContentType默认值为application/oct-srream。
                if (metadata.getContentType() == null) {
                    metadata.setContentType(Mimetypes.getInstance().getMimetype(new File(filePath), objectName));
                }
    
                // 初始化分片。
                InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
                // 返回uploadId。
                String uploadId = upresult.getUploadId();
                // 根据uploadId执行取消分片上传事件或者列举已上传分片的操作。
                // 如果您需要根据uploadId执行取消分片上传事件的操作,您需要在调用InitiateMultipartUpload完成初始化分片之后获取uploadId。 
                // 如果您需要根据uploadId执行列举已上传分片的操作,您需要在调用InitiateMultipartUpload完成初始化分片之后,且在调用CompleteMultipartUpload完成分片上传之前获取uploadId。
                // System.out.println(uploadId);
    
                // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
                List<PartETag> partETags =  new ArrayList<PartETag>();
                // 每个分片的大小,用于计算文件有多少个分片。单位为字节。
                final long partSize = 1 * 1024 * 1024L;   //1 MB。
    
                // 根据上传的数据大小计算分片数。以本地文件为例,说明如何通过File.length()获取上传数据的大小。
                final File sampleFile = new File(filePath);
                long fileLength = sampleFile.length();
                int partCount = (int) (fileLength / partSize);
                if (fileLength % partSize != 0) {
                    partCount++;
                }
                // 遍历分片上传。
                for (int i = 0; i < partCount; i++) {
                    long startPos = i * partSize;
                    long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
                    UploadPartRequest uploadPartRequest = new UploadPartRequest();
                    uploadPartRequest.setBucketName(bucketName);
                    uploadPartRequest.setKey(objectName);
                    uploadPartRequest.setUploadId(uploadId);
                    // 设置上传的分片流。
                    // 以本地文件为例说明如何创建FIleInputstream,并通过InputStream.skip()方法跳过指定数据。
                    InputStream instream = new FileInputStream(sampleFile);
                    instream.skip(startPos);
                    uploadPartRequest.setInputStream(instream);
                    // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。
                    uploadPartRequest.setPartSize(curPartSize);
                    // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,OSS将返回InvalidArgument错误码。
                    uploadPartRequest.setPartNumber( i + 1);
                    // 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。
                    UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
                    // 每次上传分片之后,OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
                    partETags.add(uploadPartResult.getPartETag());
                }
    
    
                // 创建CompleteMultipartUploadRequest对象。
                // 在执行完成分片上传操作时,需要提供所有有效的partETags。OSS收到提交的partETags后,会逐一验证每个分片的有效性。当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。
                CompleteMultipartUploadRequest completeMultipartUploadRequest =
                        new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
    
                // 如果需要在完成分片上传的同时设置文件访问权限,请参考以下示例代码。
                // completeMultipartUploadRequest.setObjectACL(CannedAccessControlList.Private);
                // 指定是否列举当前UploadId已上传的所有Part。仅在Java SDK为3.14.0及以上版本时,支持通过服务端List分片数据来合并完整文件时,将CompleteMultipartUploadRequest中的partETags设置为null。
                // Map<String, String> headers = new HashMap<String, String>();
                // 如果指定了x-oss-complete-all:yes,则OSS会列举当前UploadId已上传的所有Part,然后按照PartNumber的序号排序并执行CompleteMultipartUpload操作。
                // 如果指定了x-oss-complete-all:yes,则不允许继续指定body,否则报错。
                // headers.put("x-oss-complete-all","yes");
                // completeMultipartUploadRequest.setHeaders(headers);
    
                // 完成分片上传。
                CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
                System.out.println(completeMultipartUploadResult.getETag());
            } catch (OSSException oe) {
                System.out.println("Caught an OSSException, which means your request made it to OSS, "
                        + "but was rejected with an error response for some reason.");
                System.out.println("Error Message:" + oe.getErrorMessage());
                System.out.println("Error Code:" + oe.getErrorCode());
                System.out.println("Request ID:" + oe.getRequestId());
                System.out.println("Host ID:" + oe.getHostId());
            } catch (ClientException ce) {
                System.out.println("Caught an ClientException, which means the client encountered "
                        + "a serious internal problem while trying to communicate with OSS, "
                        + "such as not being able to access the network.");
                System.out.println("Error Message:" + ce.getMessage());
            } finally {
                if (ossClient != null) {
                    ossClient.shutdown();
                }
            }
        }
    }

步骤三:创建数据上云任务

  1. 访问RDS实例列表,在上方选择地域,然后单击目标实例ID。
  2. 在左侧菜单栏中选择备份恢复

  3. 单击页面上方的OSS备份数据恢复上云

  4. 数据导入向导页面,单击两次下一步,进入数据导入步骤。

    说明

    如果您是第一次使用OSS备份数据恢复上云功能,需要给RDS官方服务账号授予访问OSS的权限,请单击授权地址并同意授权,否则会因权限问题导致OSS Bucket下拉列表为空。

  5. 设置如下参数。

    配置项

    说明

    数据库名

    备份数据导入目标RDS实例上的数据库名,名称需要符合SQL Server官方限制。

    重要
    • 进行上云操作前,请确保目标实例上不存在与备份文件指定要还原的数据库名称相同的数据库,也不存在相同名称的未附加数据库文件。若都不存在,则可以使用备份集中同名数据库文件名称还原数据库。

    • 如果目标实例上存在与备份文件指定要还原的数据库名称相同的数据库,或者存在同名的未附加数据库文件,上云操作将失败。

    OSS Bucket

    选择备份文件所在的OSS Bucket。

    OSS子文件夹名

    备份文件所在的子文件夹名。

    OSS文件列表

    单击右侧放大镜按钮,可以按照备份文件名前缀模糊查找,会展示文件名、文件大小和更新时间。请选择需要上云的备份文件。

    上云方案

    • 打开数据库(只有一个全量备份文件):全量上云,适合仅有一个完全备份文件上云的场景。本操作选择打开数据库,此时CreateMigrateTask中的BackupMode = FULL并且IsOnlineDB = True

    • 不打开数据库(还有差异备份或日志文件):增量上云,适合有完全备份文件加上日志备份(或者差异备份文件)上云的场景,此时CreateMigrateTask中的BackupMode = UPDF 并且IsOnlineDB = False

    一致性检查方式

    • 异步执行DBCC:在打开数据库的时候系统不做DBCC CheckDB,会在打开数据库任务结束以后,异步执行DBCC CheckDB操作,以此来节约打开数据库操作的时间开销(数据库比较大,DBCC CheckDB非常耗时),减少您的业务停机时间。如果您对业务停机时间要求非常敏感,且不关心DBCC CheckDB结果,建议使用异步执行DBCC。此时CreateMigrateTask 中的CheckDBMode = AsyncExecuteDBCheck

    • 同步执行DBCC:相对于异步执行DBCC,有的用户非常关心DBCC CheckDB的结果,以此来找出用户线下数据库数据一致性错误。此时,建议您选择同步执行DBCC,影响是会拉长打开数据库的时间。此时CreateMigrateTask 中的CheckDBMode = SyncExecuteDBCheck

  6. 单击确定

    请耐心等待上云任务完成,您可以单击刷新查看数据上云任务最新状态。如果上云失败,请根据任务描述提示排查错误,具体可参见本文的常见错误

步骤四:查看备份上云记录

您可以在备份恢复页面备份数据上云记录页签内查看备份上云记录,默认会展示最近一周的记录。

常见错误

每一条备份上云恢复记录中,都会有任务描述信息,可以通过这些描述信息提示来发现任务失败或报错的原因,常见的错误信息如下:

  • 同名数据库已经存在

    • 错误信息:The database (xxx) is already exist on RDS, please backup and drop it, then try again.

    • 错误原因:为了保证RDS SQL Server上数据的安全性,RDS SQL Server不支持同名数据库的上云操作。

    • 解决方法:如果您确实需要对现有数据库的数据进行覆盖,请自行先备份已经存在的数据,然后删除数据库,最后再重新数据上云任务。

  • 使用差异备份文件

    • 错误信息:Backup set (xxx.bak) is a Database Differential backup, we only accept a FULL Backup.

    • 错误原因:您提供的备份文件是差异备份,不是全量备份文件,一次性全量迁入上云仅支持全量备份文件,不支持差异备份。

  • 使用日志备份文件

    • 错误信息:Backup set (xxx.trn) is a Transaction Log backup, we only accept a FULL Backup.

    • 错误原因:您提供的备份文件是日志备份,不是全量备份文件,一次性全量迁入上云仅支持全量备份文件,不支持日志备份。

  • 备份文件校验失败

    • 错误信息:Failed to verify xxx.bak, backup file was corrupted or newer edition than RDS.

    • 错误原因:备份文件损坏或者备份文件所在的本地环境SQL Server实例版本比RDS SQL Server版本高,导致校验失败。例如将一个SQL Server 2016的备份还原到RDS SQL Server 2012版本,就会报告这个错误。

    • 解决方法:如果是备份文件损坏,请在本地环境重新做一个全量备份,重新生成迁移上云任务;如果是版本过高,请使用与本地环境版本一致或者更高的RDS SQL Server。

  • DBCC CHECKDB失败

    • 错误信息:DBCC checkdb failed.

    • 错误原因:DBCC CheckDB检查操作报错,说明数据库在本地环境中已经有错误发生。

    • 解决方法:使用如下命令修复本地环境数据库错误后重新上云。

      说明

      使用该命令修复错误的过程,可能会导致数据丢失。

      DBCC CHECKDB (DBName, REPAIR_ALLOW_DATA_LOSS) WITH NO_INFOMSGS, ALL_ERRORMSGS
  • 空间不足1

    • 错误信息:Not Enough Disk Space for restoring, space left (xxx MB) < needed (xxx MB).

    • 错误原因:RDS实例剩余空间不满足备份文件上云所需要的最小空间要求。

    • 解决方法:升级实例空间。

  • 空间不足2

    • 错误信息:Not Enough Disk Space, space left xxx MB < bak file xxx MB.

    • 错误原因:RDS实例剩余空间比备份文件本身小,不满足最小空间要求。

    • 解决方法:升级实例空间。

  • 没有高权限账号

    • 错误信息:Your RDS doesn’t have any init account yet, please create one and grant permissions on RDS console to this migrated database (XXX).

    • 错误原因:RDS实例不存在高权限账号,OSS备份数据上云任务不知道需要为哪个用户授权,但是备份文件已经成功还原到目标实例上,所以任务状态是成功的。

    • 解决方法:创建高权限账号,具体操作,请参见创建数据库和账号(SQL Server 2012、2014、2016、2017和2019)

  • RAM账号操作权限不足

    Q:步骤三:创建数据上云任务的步骤5中,各配置项参数均已填写完整,但确定按钮为灰色无法单击?

    A:无法单击的原因可能是您为RAM用户,您的账号权限不足。请参见本文前提条件,确保相应权限已授予。

常见返回信息如下:

任务类型

任务状态

任务描述

说明

全量备份文件一次性迁入

成功

success

上云成功。

失败

Failed to download backup file since OSS URL was expired.

OSS下载URL有效期过期,导致上云失败。

Your backup is corrupted or newer than RDS, failed to verify.

备份文件损坏或者比RDS的版本更高,导致上云失败。

DBCC checkdb failed

DBCC checkdb失败,导致上云失败。

autotest_2008r2_std_testmigrate_log.trn is a Transaction Log backup, we only accept a FULL Backup.

日志备份,导致上云失败。

autotest_2008r2_std_testmigrate_diff.bak is a Database Differential backup, we only accept a FULL Backup.

差异备份,导致上云失败。

相关API

API

描述

CreateMigrateTask

将OSS上的备份文件还原到RDS SQL Server实例,创建数据上云任务。

CreateOnlineDatabaseTask

打开RDS SQL Server备份数据上云任务的数据库。

DescribeMigrateTasks

查询RDS SQL Server实例备份数据上云任务列表。

DescribeOssDownloads

查询RDS SQL Server备份数据上云任务的文件详情。