将 SQL 关系型数据迁移到 MongoDB 的循序渐进指南

通过这份全面的循序渐进指南,了解如何将您的 SQL 关系型数据迁移到 MongoDB。探索将传统模式转换为高效 MongoDB 文档结构的最佳实践,包括必要的规划、嵌入和引用等模式设计策略、数据提取、转换技术以及加载到 MongoDB。本教程提供了实用的示例和可操作的建议,以实现向 NoSQL 数据库的平稳成功过渡。

40 浏览量

SQL关系型数据迁移到MongoDB的分步指南

从SQL等关系型数据库迁移到MongoDB等NoSQL文档数据库是一项常见但通常很复杂的工作。关系型数据库擅长通过结构化表、外键和ACID事务来强制执行数据完整性。另一方面,MongoDB通过使用面向文档的数据模型,为某些工作负载提供了灵活性、可扩展性和性能优势。本指南提供了一种实用的分步方法,用于将传统的关系型模式转换为高效的MongoDB文档结构,涵盖了基本的模式设计注意事项和工具,以实现平稳过渡。

理解这些数据库范式之间的基本差异对于成功的迁移至关重要。关系型模式通常是规范化的,将数据分解为多个表以减少冗余。然而,MongoDB的文档模型鼓励反规范化,将相关数据嵌入到单个文档中,以提高读取性能并简化应用程序逻辑。这种转变需要仔细规划,以设计与应用程序访问模式相符的文档。

理解核心差异:关系型模型与文档模型

在深入研究迁移过程之前,必须掌握概念上的差异:

  • 关系型模型: 数据存储在具有预定义模式的表中。关系通过外键管理,需要执行JOIN操作来检索相关数据。规范化是一个关键原则。
  • 文档模型(MongoDB): 数据存储在灵活的、类似JSON的文档中。文档的结构可以不同。相关数据可以嵌入到单个文档中(反规范化),或使用应用程序级联接或MongoDB的$lookup聚合阶段进行引用。

这种数据建模的差异直接影响您如何设计MongoDB集合和文档。

阶段 1:规划和模式设计

这是最关键的阶段。设计良好的MongoDB模式是利用其优势的关键。目标是根据应用程序访问模式来建模数据,而不仅仅是直接翻译SQL表。

1. 分析应用程序的访问模式

  • 确定读密集型与写密集型操作: 数据的读取频率如何?通常如何查询数据?哪些字段最常一起检索?
  • 确定常用查询路径: SQL应用程序中最常见的SELECT语句是什么?通常会联接哪些表?
  • 理解数据关系: 实体之间如何关联?它们是一对一、一对多还是多对多关系?

2. 选择反规范化策略

MongoDB的强大功能在于其嵌入相关数据的能力。请考虑以下策略:

  • 嵌入(反规范化): 最常见的方法。当关系为一对多或数据经常一起访问时,将文档或文档数组嵌入到父文档中。这减少了对联接的需求。
    • 示例: 与其拥有单独的ordersorder_items表,不如将order_items作为数组嵌入到order文档中。
  • 引用: 当嵌入会导致文档过大,或者数据被独立访问时使用。存储相关文档的_id(类似于外键),然后执行应用程序级联接或使用MongoDB的$lookup
    • 示例: users集合和posts集合。帖子可能存储其作者的user_id。然后,在获取帖子时,您可以使用$lookup来检索作者的详细信息。

3. 设计MongoDB集合和文档

根据您的访问模式和反规范化策略,设计您的集合。一个好的起点是将SQL表映射到MongoDB集合。然后,决定哪些相关数据应该被嵌入,哪些应该被引用。

SQL模式示例:

-- 客户表
CREATE TABLE Customers (
    CustomerID INT PRIMARY KEY,
    FirstName VARCHAR(50),
    LastName VARCHAR(50),
    Email VARCHAR(100)
);

-- 订单表
CREATE TABLE Orders (
    OrderID INT PRIMARY KEY,
    CustomerID INT,
    OrderDate DATE,
    TotalAmount DECIMAL(10, 2),
    FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID)
);

-- 订单项表
CREATE TABLE OrderItems (
    OrderItemID INT PRIMARY KEY,
    OrderID INT,
    ProductID INT,
    Quantity INT,
    Price DECIMAL(10, 2),
    FOREIGN KEY (OrderID) REFERENCES Orders(OrderID)
);

MongoDB文档设计选项:

  • **选项 A:带有嵌入式订单的客户(如果客户拥有的订单数量可管理,并且经常与客户一起查看订单):
    json { "_id": ObjectId("..."), "customer_id": 1, "first_name": "John", "last_name": "Doe", "email": "[email protected]", "orders": [ { "order_id": 101, "order_date": ISODate("2023-10-26T00:00:00Z"), "total_amount": 50.00, "items": [ { "product_id": 1, "quantity": 2, "price": 25.00 }, { "product_id": 3, "quantity": 1, "price": 0.00 } // 免费商品的示例 ] }, // ... 更多订单 ] }
  • 选项 B:带有引用的独立集合(如果订单数量很多或经常独立查询):
    Customers Collection:
    json { "_id": ObjectId("..."), "customer_id": 1, "first_name": "John", "last_name": "Doe", "email": "[email protected]" }
    Orders Collection:**
    json { "_id": ObjectId("..."), "order_id": 101, "customer_id": 1, // 对Customers集合的引用 "order_date": ISODate("2023-10-26T00:00:00Z"), "total_amount": 50.00, "items": [ { "product_id": 1, "quantity": 2, "price": 25.00 }, { "product_id": 3, "quantity": 1, "price": 0.00 } ] }

文档大小注意事项: MongoDB有文档大小限制(16MB)。避免嵌入可能超出此限制的过大数组。如果数组无限增长,请考虑将其拆分成单独的集合。

阶段 2:数据提取和转换

设计好目标模式后,需要从SQL数据库中提取数据并将其转换为新的文档格式。

1. 从SQL提取数据

使用标准的SQL查询来选择所需的数据。您可以将这些数据导出为CSV或JSON等格式。

  • 使用SQL客户端: 大多数SQL数据库工具(例如DBeaver、SQL Developer、pgAdmin)允许您将查询结果导出到CSV或JSON。
  • 脚本编写: 编写脚本(Python、Node.js等)来连接到SQL数据库、执行查询并获取数据。

2. 转换数据

这是您实现设计模式的地方。您需要编写代码或使用工具来:

  • 分组相关记录: 例如,收集属于特定Order的所有OrderItems
  • 重构数据: 将关系行转换为嵌套的JSON文档。
  • 处理数据类型: 确保数据类型与MongoDB兼容(例如,日期、数字、字符串)。

Python示例:

假设您已将CustomersOrdersOrderItems导出到CSV文件。

import pandas as pd
import json
from bson import ObjectId # 用于MongoDB的ObjectId,尽管对于直接转换并非绝对必需

# 从CSV文件加载数据(假设它们在同一目录中)
customers_df = pd.read_csv('customers.csv')
orders_df = pd.read_csv('orders.csv')
order_items_df = pd.read_csv('order_items.csv')

# --- 数据转换逻辑 ---

# 将DataFrame转换为字典以便于操作
customers_list = customers_df.to_dict('records')
orders_list = orders_df.to_dict('records')
order_items_list = order_items_df.to_dict('records')

# 创建订单和订单项的映射以快速查找
orders_by_customer = {}
for order in orders_list:
    customer_id = order['CustomerID']
    if customer_id not in orders_by_customer:
        orders_by_customer[customer_id] = []
    orders_by_customer[customer_id].append(order)

order_items_by_order = {}
for item in order_items_list:
    order_id = item['OrderID']
    if order_id not in order_items_by_order:
        order_items_by_order[order_id] = []
    order_items_by_order[order_id].append(item)

# --- 构建MongoDB文档(选项A:带有嵌入式订单的客户)---
mongo_documents = []

for customer in customers_list:
    mongo_doc = {
        "_id": ObjectId(), # MongoDB自动生成_id,但如果需要可以映射
        "customer_id": customer['CustomerID'],
        "first_name": customer['FirstName'],
        "last_name": customer['LastName'],
        "email": customer['Email'],
        "orders": []
    }

    customer_id = customer['CustomerID']
    if customer_id in orders_by_customer:
        for order in orders_by_customer[customer_id]:
            order_doc = {
                "order_id": order['OrderID'],
                "order_date": order['OrderDate'], # 确保正确的日期格式
                "total_amount": order['TotalAmount'],
                "items": []
            }

            order_id = order['OrderID']
            if order_id in order_items_by_order:
                for item in order_items_by_order[order_id]:
                    order_doc['items'].append({
                        "product_id": item['ProductID'],
                        "quantity": item['Quantity'],
                        "price": item['Price']
                    })
            mongo_doc['orders'].append(order_doc)

    mongo_documents.append(mongo_doc)

# 现在'mongo_documents'是一个准备插入到MongoDB的字典列表
# print(json.dumps(mongo_documents[0], indent=2, default=str)) # 将第一个文档打印为JSON

# 对于选项B(独立集合),您将为每个集合创建列表:
# customers_mongo = [{'customer_id': c['CustomerID'], ...} for c in customers_list]
# orders_mongo = [{'order_id': o['OrderID'], 'customer_id': o['CustomerID'], ...} for o in orders_list]

# 保存到JSON以供导入(可选)
# with open('mongo_customer_data.json', 'w') as f:
#     json.dump(mongo_documents, f, indent=2, default=str)

3. 转换工具

  • 自定义脚本: 使用Pandas的Python、使用csv-parsermysql/pg等库的Node.js,对于复杂的转换非常强大。
  • ETL工具: Apache NiFi、Talend或AWS Glue等工具可以协调复杂的数据管道,包括SQL到MongoDB的迁移。
  • MongoDB Atlas实时迁移: 如果迁移到MongoDB Atlas,其实时迁移服务可以协助从各种源(包括SQL数据库)移动数据。

阶段 3:数据加载到MongoDB

转换好数据后,就可以将其加载到MongoDB实例中了。

1. 连接到MongoDB

使用MongoDB Shell(mongosh)或MongoDB驱动程序(适用于您的编程语言)连接到数据库。

2. 导入转换后的数据

  • 使用mongoimportmongosh 如果您已将转换后的数据导出到JSON文件(如Python示例所示),则可以使用mongoimport
    bash # 假设您的数据在mongo_customer_data.json中,并且您想导入到'customers'集合中 mongoimport --db your_database_name --collection customers --file mongo_customer_data.json --jsonArray

    • --jsonArray:如果您的JSON文件包含文档数组,请使用此标志。
  • 使用MongoDB驱动程序: 如果您在编程语言中生成了数据结构(如Python脚本中的mongo_documents列表),则可以直接插入它们:

    **Python示例(使用pymongo):
    ```python
    from pymongo import MongoClient

    假设'mongo_documents'列表由上一个Python脚本定义

    client = MongoClient('mongodb://localhost:27017/')
    db = client['your_database_name']
    customers_collection = db['customers']

    插入转换后的文档

    if mongo_documents:
    insert_result = customers_collection.insert_many(mongo_documents)
    print(f"Inserted {len(insert_result.inserted_ids)} documents.")
    else:
    print("No documents to insert.")

    client.close()
    ```

3. 验证数据完整性

加载后,在MongoDB中运行查询以验证数据是否已正确导入并符合您的期望。

// 示例:计算'customers'集合中文档数量
use your_database_name;
print(db.customers.countDocuments());

// 示例:查找特定客户并检查其嵌入的订单
db.customers.findOne({ "customer_id": 1 })

阶段 4:应用程序重构

这可以说是最耗时的阶段。需要更新应用程序代码以与MongoDB交互,而不是SQL。

  • 更新数据库连接: 更改连接字符串和库。
  • 重写查询: 使用MongoDB查询语言替换SQL查询,使用您选择的驱动程序的API。
  • 调整数据访问层: 修改您的ORM或数据访问层以使用MongoDB文档。
  • 利用MongoDB特性: 使您的应用程序能够利用灵活的模式、聚合框架和地理空间查询(如果适用)等特性。

最佳实践和技巧

  • 从小处着手: 如果可能,首先迁移部分数据或非关键应用程序,以获得经验。
  • 迭代模式设计: 您最初的MongoDB模式可能不完美。准备好根据性能测试和应用程序反馈进行迭代和完善。
  • 明智地建立索引: 就像在SQL中一样,索引对于MongoDB的性能至关重要。确定您的查询模式并创建适当的索引。
  • 监控性能: 持续监控您的MongoDB部署的性能瓶颈,并根据需要优化查询和模式。
  • 考虑增量迁移: 对于大型数据库,考虑一种增量迁移策略,在该策略中,您近乎实时地将SQL中的更改同步到MongoDB,然后再进行最终切换。

结论

从SQL迁移到MongoDB是一项战略性举措,可以在灵活性和可扩展性方面带来显著的优势。该过程需要仔细的规划、以应用程序访问模式为中心的周到的模式设计,以及稳健的转换和加载策略。通过遵循这些步骤和最佳实践,您可以驾驭将关系数据转换为高效且强大的MongoDB文档模型的复杂性,为更敏捷和可扩展的应用程序架构铺平道路。