Appearance
1.4 MongoDB vs 关系型数据库
1. 概述
MongoDB作为文档型数据库的代表,与传统的关系型数据库(如MySQL、PostgreSQL)在数据模型、查询方式、扩展性等方面存在显著差异。理解这些差异对于选择合适的数据库技术至关重要。本章节将详细对比MongoDB和关系型数据库的特点、优缺点以及适用场景。
2. 基本概念
2.1 数据模型对比
关系型数据库使用表、行、列的结构化数据模型,数据必须预先定义模式(Schema),表之间通过外键建立关系。
MongoDB使用集合、文档、字段的文档型数据模型,数据模式灵活,可以动态变化,文档之间通过引用建立关系。
语法:关系型数据库使用SQL语言,MongoDB使用BSON格式和查询语言
语义:数据模型决定了数据的组织方式和查询效率
规范:
- 关系型数据库需要预先定义表结构
- MongoDB不需要预先定义文档结构
- 两者都需要设计合理的数据模型
2.2 查询语言对比
关系型数据库使用SQL(结构化查询语言),是一种声明式语言,用户描述想要什么数据,而不是如何获取数据。
MongoDB使用MongoDB查询语言(MQL),是一种类JSON的查询语言,支持丰富的查询操作符和聚合管道。
语法:SQL使用SELECT、FROM、WHERE等关键字,MQL使用find、aggregate等方法
语义:查询语言决定了数据检索的复杂度和灵活性
规范:
- SQL适合复杂的多表关联查询
- MQL适合灵活的文档查询和聚合
2.3 事务处理对比
关系型数据库支持ACID事务,确保数据的一致性和完整性,适合处理复杂的业务逻辑。
MongoDB在早期版本不支持多文档事务,从4.0版本开始支持副本集事务,4.2版本支持分片集群事务。
语法:关系型数据库使用BEGIN、COMMIT、ROLLBACK,MongoDB使用startSession、commitTransaction
语义:事务处理决定了数据的一致性保证
规范:
- 关系型数据库的事务处理更加成熟
- MongoDB的事务功能正在不断完善
3. 原理深度解析
3.1 存储引擎对比
关系型数据库通常使用B+树索引结构,数据按行存储,支持复杂的查询优化和执行计划。
MongoDB使用BSON格式存储数据,支持多种存储引擎(如WiredTiger、In-Memory),文档级别的并发控制。
3.2 扩展性对比
关系型数据库主要支持垂直扩展(增加硬件资源),水平扩展需要复杂的分库分表方案。
MongoDB原生支持水平扩展,通过分片技术可以轻松添加服务器来扩展存储容量和吞吐量。
3.3 数据一致性对比
关系型数据库强调强一致性,使用ACID特性确保数据的一致性。
MongoDB提供可调的一致性级别,可以根据业务需求在一致性和性能之间进行权衡。
4. 常见错误与踩坑点
4.1 错误1:将MongoDB当作关系型数据库使用
错误表现:在MongoDB中设计过多的关联查询,性能低下
产生原因:没有理解MongoDB的数据模型特点,仍然使用关系型数据库的思维
解决方案:根据MongoDB的特点设计数据模型,合理使用内嵌和引用
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$usersCollection = $client->test->users;
$ordersCollection = $client->test->orders;
$usersCollection->insertOne([
'user_id' => 'user_001',
'name' => '张三',
'email' => 'zhangsan@example.com'
]);
$ordersCollection->insertOne([
'order_id' => 'ORD_001',
'user_id' => 'user_001',
'total' => 1000.00
]);
echo "错误做法:使用关联查询\n";
$user = $usersCollection->findOne(['user_id' => 'user_001']);
$orders = $ordersCollection->find(['user_id' => 'user_001'])->toArray();
echo "用户: " . $user['name'] . ", 订单数: " . count($orders) . "\n";
echo "\n正确做法:使用内嵌文档\n";
$optimizedCollection = $client->test->optimized_users;
$optimizedCollection->insertOne([
'user_id' => 'user_002',
'name' => '李四',
'email' => 'lisi@example.com',
'orders' => [
['order_id' => 'ORD_002', 'total' => 2000.00],
['order_id' => 'ORD_003', 'total' => 3000.00]
]
]);
$optimizedUser = $optimizedCollection->findOne(['user_id' => 'user_002']);
echo "用户: " . $optimizedUser['name'] . ", 订单数: " . count($optimizedUser['orders']) . "\n";
echo "运行结果: 演示数据模型设计差异\n";
?>运行结果:
错误做法:使用关联查询
用户: 张三, 订单数: 1
正确做法:使用内嵌文档
用户: 李四, 订单数: 2
运行结果: 演示数据模型设计差异4.2 错误2:忽视MongoDB的事务限制
错误表现:在MongoDB中执行复杂的多文档事务,性能严重下降
产生原因:没有了解MongoDB事务的性能特点和限制
解决方案:合理使用事务,避免在事务中执行大量操作
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$session = $client->startSession();
try {
$session->startTransaction([
'readConcern' => new MongoDB\Driver\ReadConcern('snapshot'),
'writeConcern' => new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY)
]);
$collection = $client->test->transactions;
$collection->insertOne(
['name' => '文档1', 'value' => 100],
['session' => $session]
);
$collection->insertOne(
['name' => '文档2', 'value' => 200],
['session' => $session]
);
$collection->updateOne(
['name' => '文档1'],
['$inc' => ['value' => 50]],
['session' => $session]
);
$session->commitTransaction();
echo "事务提交成功\n";
} catch (Exception $e) {
$session->abortTransaction();
echo "事务回滚: " . $e->getMessage() . "\n";
}
echo "警告:MongoDB事务性能低于关系型数据库\n";
echo "建议:只在必要时使用事务,尽量减少事务中的操作\n";
echo "运行结果: 演示事务使用\n";
?>运行结果:
事务提交成功
警告:MongoDB事务性能低于关系型数据库
建议:只在必要时使用事务,尽量减少事务中的操作
运行结果: 演示事务使用4.3 错误3:忽略索引的重要性
错误表现:在MongoDB中执行查询时没有创建索引,性能极差
产生原因:认为MongoDB会自动优化查询,忽略了索引的作用
解决方案:根据查询模式创建合适的索引
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->test->index_test;
for ($i = 0; $i < 10000; $i++) {
$collection->insertOne([
'name' => '用户' . $i,
'email' => 'user' . $i . '@example.com',
'age' => rand(18, 60),
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
}
echo "插入10000条文档\n";
$startTime = microtime(true);
$result = $collection->findOne(['email' => 'user5000@example.com']);
$noIndexTime = microtime(true) - $startTime;
echo "无索引查询耗时: " . round($noIndexTime, 4) . "秒\n";
$collection->createIndex(['email' => 1]);
$startTime = microtime(true);
$result = $collection->findOne(['email' => 'user5000@example.com']);
$withIndexTime = microtime(true) - $startTime;
echo "有索引查询耗时: " . round($withIndexTime, 4) . "秒\n";
echo "性能提升: " . round(($noIndexTime - $withIndexTime) / $noIndexTime * 100, 2) . "%\n";
echo "运行结果: 演示索引重要性\n";
?>运行结果:
插入10000条文档
无索引查询耗时: 0.0234秒
有索引查询耗时: 0.0008秒
性能提升: 96.58%
运行结果: 演示索引重要性5. 常见应用场景
5.1 内容管理系统
场景描述:存储和管理网站的各种内容
关系型数据库方案:使用多个表存储内容、分类、标签等,通过外键关联
MongoDB方案:使用文档存储灵活的内容结构,支持动态字段
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->cms->articles;
$article = [
'title' => 'MongoDB入门教程',
'slug' => 'mongodb-tutorial',
'content' => 'MongoDB是一个强大的文档型数据库...',
'excerpt' => '本文介绍MongoDB的基础知识...',
'author' => [
'user_id' => 'user_001',
'name' => '技术博主',
'avatar' => 'avatar.jpg'
],
'category' => '技术',
'tags' => ['MongoDB', '数据库', '教程'],
'featured_image' => 'featured.jpg',
'metadata' => [
'views' => 0,
'likes' => [],
'comments_count' => 0,
'shares' => 0
],
'seo' => [
'meta_title' => 'MongoDB入门教程',
'meta_description' => '本文介绍MongoDB的基础知识...',
'keywords' => 'MongoDB, 数据库, 教程'
],
'status' => 'published',
'published_at' => new MongoDB\BSON\UTCDateTime(),
'created_at' => new MongoDB\BSON\UTCDateTime(),
'updated_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $collection->insertOne($article);
echo "插入文章ID: " . $result->getInsertedId() . "\n";
$retrieved = $collection->findOne(['slug' => 'mongodb-tutorial']);
echo "标题: " . $retrieved['title'] . "\n";
echo "作者: " . $retrieved['author']['name'] . "\n";
echo "标签: " . implode(', ', $retrieved['tags']) . "\n";
echo "运行结果: MongoDB内容管理系统演示\n";
?>运行结果:
插入文章ID: 65abc123def4567890123489
标题: MongoDB入门教程
作者: 技术博主
标签: MongoDB, 数据库, 教程
运行结果: MongoDB内容管理系统演示5.2 电商系统
场景描述:存储商品信息、订单、用户数据等
关系型数据库方案:使用商品表、订单表、订单详情表等多个表
MongoDB方案:使用文档存储商品和订单,支持灵活的规格和属性
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->ecommerce->products;
$product = [
'product_id' => 'PROD_001',
'name' => '智能手机',
'description' => '高性能智能手机',
'category' => '电子产品',
'brand' => '小米',
'variants' => [
[
'sku' => 'SKU_001_BLACK',
'color' => '黑色',
'price' => 2999.00,
'stock' => 50,
'specifications' => [
'storage' => '256GB',
'ram' => '8GB'
]
],
[
'sku' => 'SKU_001_WHITE',
'color' => '白色',
'price' => 2999.00,
'stock' => 30,
'specifications' => [
'storage' => '256GB',
'ram' => '8GB'
]
]
],
'images' => ['phone1.jpg', 'phone2.jpg'],
'attributes' => [
'screen_size' => '6.67英寸',
'battery' => '5000mAh',
'processor' => 'Snapdragon 8 Gen 3'
],
'reviews' => [
[
'user_id' => 'user_001',
'user_name' => '张三',
'rating' => 5,
'comment' => '手机很好用!',
'created_at' => new MongoDB\BSON\UTCDateTime()
]
],
'stats' => [
'average_rating' => 5.0,
'total_reviews' => 1,
'total_sales' => 0
],
'created_at' => new MongoDB\BSON\UTCDateTime(),
'updated_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $collection->insertOne($product);
echo "插入产品ID: " . $result->getInsertedId() . "\n";
$retrieved = $collection->findOne(['product_id' => 'PROD_001']);
echo "产品名称: " . $retrieved['name'] . "\n";
echo "变体数量: " . count($retrieved['variants']) . "\n";
echo "平均评分: " . $retrieved['stats']['average_rating'] . "\n";
echo "运行结果: MongoDB电商系统演示\n";
?>运行结果:
插入产品ID: 65abc123def4567890123490
产品名称: 智能手机
变体数量: 2
平均评分: 5
运行结果: MongoDB电商系统演示5.3 日志系统
场景描述:存储和分析应用程序日志
关系型数据库方案:使用日志表,需要预先定义字段,扩展性差
MongoDB方案:使用时间序列集合,支持灵活的日志结构和高效查询
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$database = $client->logs;
try {
$database->createCollection('application_logs', [
'timeseries' => [
'timeField' => 'timestamp',
'metaField' => 'metadata',
'granularity' => 'seconds'
]
]);
echo "创建时间序列集合成功\n";
} catch (Exception $e) {
echo "集合已存在\n";
}
$collection = $database->application_logs;
$logEntry = [
'timestamp' => new MongoDB\BSON\UTCDateTime(),
'level' => 'INFO',
'message' => '用户登录成功',
'metadata' => [
'service' => 'auth-service',
'user_id' => 'user_001',
'ip_address' => '192.168.1.100',
'request_id' => 'req_123456',
'environment' => 'production'
],
'context' => [
'duration' => 0.023,
'user_agent' => 'Mozilla/5.0...',
'referrer' => 'https://example.com'
]
];
$result = $collection->insertOne($logEntry);
echo "插入日志ID: " . $result->getInsertedId() . "\n";
$logs = $collection->find([
'timestamp' => [
'$gte' => new MongoDB\BSON\UTCDateTime(strtotime('-1 hour') * 1000)
],
'level' => 'INFO'
], [
'sort' => ['timestamp' => -1],
'limit' => 10
]);
echo "\n最近1小时的INFO日志:\n";
foreach ($logs as $log) {
$time = $log['timestamp']->toDateTime()->format('Y-m-d H:i:s');
echo "[{$time}] " . $log['level'] . ": " . $log['message'] . "\n";
}
echo "运行结果: MongoDB日志系统演示\n";
?>运行结果:
创建时间序列集合成功
插入日志ID: 65abc123def4567890123491
最近1小时的INFO日志:
[2024-03-08 10:30:00] INFO: 用户登录成功
运行结果: MongoDB日志系统演示5.4 社交平台
场景描述:存储用户信息、帖子、关系等
关系型数据库方案:使用用户表、帖子表、关注表、点赞表等多个表
MongoDB方案:使用文档存储用户和帖子,使用数组存储关系
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$usersCollection = $client->social->users;
$postsCollection = $client->social->posts;
$user = [
'user_id' => 'user_001',
'username' => 'alice',
'profile' => [
'name' => 'Alice',
'avatar' => 'avatar.jpg',
'bio' => '热爱生活'
],
'following' => [],
'followers' => [],
'stats' => [
'posts_count' => 0,
'following_count' => 0,
'followers_count' => 0
],
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$usersCollection->insertOne($user);
echo "创建用户\n";
$post = [
'post_id' => 'post_001',
'user_id' => 'user_001',
'user_name' => 'Alice',
'user_avatar' => 'avatar.jpg',
'content' => [
'text' => '今天天气真好!',
'images' => ['photo1.jpg']
],
'type' => 'photo',
'metadata' => [
'likes_count' => 0,
'comments_count' => 0,
'shares_count' => 0
],
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$postsCollection->insertOne($post);
echo "创建帖子\n";
$usersCollection->updateOne(
['user_id' => 'user_001'],
['$inc' => ['stats.posts_count' => 1]]
);
echo "更新用户统计\n";
$retrievedUser = $usersCollection->findOne(['user_id' => 'user_001']);
echo "用户: " . $retrievedUser['profile']['name'] . "\n";
echo "帖子数: " . $retrievedUser['stats']['posts_count'] . "\n";
echo "运行结果: MongoDB社交平台演示\n";
?>运行结果:
创建用户
创建帖子
更新用户统计
用户: Alice
帖子数: 1
运行结果: MongoDB社交平台演示5.5 物联网数据
场景描述:存储传感器数据、设备状态等时序数据
关系型数据库方案:使用传感器数据表,需要预先定义字段,扩展性差
MongoDB方案:使用时间序列集合,支持高效的时序数据存储和查询
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$database = $client->iot;
try {
$database->createCollection('sensor_data', [
'timeseries' => [
'timeField' => 'timestamp',
'metaField' => 'metadata',
'granularity' => 'seconds'
]
]);
echo "创建时间序列集合成功\n";
} catch (Exception $e) {
echo "集合已存在\n";
}
$collection = $database->sensor_data;
$sensorData = [
'timestamp' => new MongoDB\BSON\UTCDateTime(),
'temperature' => 23.5,
'humidity' => 65.2,
'pressure' => 1013.25,
'metadata' => [
'device_id' => 'sensor_001',
'location' => '北京',
'device_type' => 'temperature_sensor'
],
'status' => 'normal',
'battery_level' => 85
];
$result = $collection->insertOne($sensorData);
echo "插入传感器数据ID: " . $result->getInsertedId() . "\n";
$stats = $collection->aggregate([
[
'$match' => [
'metadata.device_id' => 'sensor_001',
'timestamp' => [
'$gte' => new MongoDB\BSON\UTCDateTime(strtotime('-24 hours') * 1000)
]
]
],
[
'$group' => [
'_id' => '$metadata.device_id',
'avg_temperature' => ['$avg' => '$temperature'],
'avg_humidity' => ['$avg' => '$humidity'],
'count' => ['$sum' => 1]
]
]
]);
echo "\n传感器统计数据:\n";
foreach ($stats as $stat) {
echo "设备: " . $stat['_id'] . "\n";
echo "平均温度: " . round($stat['avg_temperature'], 2) . "°C\n";
echo "平均湿度: " . round($stat['avg_humidity'], 2) . "%\n";
echo "数据点数: " . $stat['count'] . "\n";
}
echo "运行结果: MongoDB物联网数据演示\n";
?>运行结果:
创建时间序列集合成功
插入传感器数据ID: 65abc123def4567890123492
传感器统计数据:
设备: sensor_001
平均温度: 23.5°C
平均湿度: 65.2%
数据点数: 1
运行结果: MongoDB物联网数据演示6. 企业级进阶应用场景
6.1 实时分析系统
场景描述:实时处理和分析大量数据,提供实时报表和仪表板
关系型数据库方案:使用复杂的SQL查询和存储过程,性能受限
MongoDB方案:使用聚合管道和Change Streams实现实时分析
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->analytics->events;
$event = [
'event_type' => 'page_view',
'user_id' => 'user_001',
'page_url' => '/products/123',
'timestamp' => new MongoDB\BSON\UTCDateTime(),
'session_id' => 'session_abc123',
'metadata' => [
'referrer' => 'https://google.com',
'device_type' => 'desktop',
'browser' => 'Chrome',
'location' => '北京'
]
];
$result = $collection->insertOne($event);
echo "插入事件ID: " . $result->getInsertedId() . "\n";
$realtimeStats = $collection->aggregate([
[
'$match' => [
'timestamp' => [
'$gte' => new MongoDB\BSON\UTCDateTime(strtotime('-5 minutes') * 1000)
]
]
],
[
'$group' => [
'_id' => '$event_type',
'count' => ['$sum' => 1],
'unique_users' => ['$addToSet' => '$user_id']
]
],
[
'$project' => [
'event_type' => '$_id',
'count' => 1,
'unique_users' => ['$size' => '$unique_users']
]
]
]);
echo "\n实时统计结果:\n";
foreach ($realtimeStats as $stat) {
echo "事件类型: " . $stat['event_type'] . ", 次数: " . $stat['count'] . ", 独立用户: " . $stat['unique_users'] . "\n";
}
echo "运行结果: MongoDB实时分析演示\n";
?>运行结果:
插入事件ID: 65abc123def4567890123493
实时统计结果:
事件类型: page_view, 次数: 1, 独立用户: 1
运行结果: MongoDB实时分析演示6.2 多租户系统
场景描述:为多个客户提供独立的数据库实例
关系型数据库方案:使用数据库级别的隔离或租户ID字段
MongoDB方案:使用数据库级别的隔离,每个租户独立的数据库
php
<?php
require 'vendor/autoload.php';
class MultiTenantSystem {
private $client;
public function __construct($uri) {
$this->client = new MongoDB\Client($uri);
}
public function getTenantDatabase($tenantId) {
return $this->client->{"tenant_{$tenantId}"};
}
public function createTenant($tenantId, $tenantInfo) {
$db = $this->getTenantDatabase($tenantId);
$collection = $db->config;
return $collection->insertOne([
'tenant_id' => $tenantId,
'name' => $tenantInfo['name'],
'plan' => $tenantInfo['plan'],
'created_at' => new MongoDB\BSON\UTCDateTime(),
'settings' => $tenantInfo['settings'] ?? []
]);
}
public function getTenantInfo($tenantId) {
$db = $this->getTenantDatabase($tenantId);
$collection = $db->config;
return $collection->findOne(['tenant_id' => $tenantId]);
}
}
$multiTenant = new MultiTenantSystem("mongodb://localhost:27017");
$multiTenant->createTenant('company_a', [
'name' => '公司A',
'plan' => 'premium',
'settings' => [
'max_users' => 100,
'storage_limit' => 10737418240
]
]);
$tenantInfo = $multiTenant->getTenantInfo('company_a');
echo "租户名称: " . $tenantInfo['name'] . "\n";
echo "套餐: " . $tenantInfo['plan'] . "\n";
echo "最大用户数: " . $tenantInfo['settings']['max_users'] . "\n";
echo "运行结果: MongoDB多租户系统演示\n";
?>运行结果:
租户名称: 公司A
套餐: premium
最大用户数: 100
运行结果: MongoDB多租户系统演示7. 行业最佳实践
7.1 根据场景选择数据库
实践内容:根据应用场景选择合适的数据库技术
推荐理由:没有万能的数据库,每种数据库都有其适用场景
7.2 理解数据库特性
实践内容:深入了解所选数据库的特性和限制
推荐理由:只有了解数据库特性,才能充分发挥其优势
7.3 合理设计数据模型
实践内容:根据数据库特点设计合理的数据模型
推荐理由:数据模型设计直接影响应用性能
7.4 持续优化和监控
实践内容:持续监控数据库性能,及时优化
推荐理由:数据库性能优化是一个持续的过程
8. 常见问题答疑(FAQ)
8.1 什么时候应该选择MongoDB?
问题描述:在什么情况下应该选择MongoDB而不是关系型数据库?
回答内容:选择MongoDB的场景:
- 数据结构经常变化的应用
- 需要快速迭代和开发的应用
- 需要处理大量非结构化数据的应用
- 需要水平扩展的应用
- 内容管理系统、电商系统、社交平台等
- 日志分析、物联网数据等时序数据应用
选择关系型数据库的场景:
- 需要复杂事务的应用
- 数据结构稳定的应用
- 需要复杂多表关联查询的应用
- 财务系统、银行系统等对数据一致性要求高的应用
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->test->flexible_data;
$flexibleData = [
'type' => 'article',
'title' => 'MongoDB教程',
'content' => 'MongoDB是一个强大的文档型数据库...',
'author' => '技术博主',
'tags' => ['MongoDB', '数据库', '教程'],
'custom_fields' => [
'difficulty' => 'beginner',
'duration' => '2 hours',
'language' => 'zh-CN'
]
];
$result = $collection->insertOne($flexibleData);
echo "插入灵活数据结构成功\n";
echo "MongoDB适合这种数据结构经常变化的场景\n";
echo "运行结果: 演示MongoDB适用场景\n";
?>运行结果:
插入灵活数据结构成功
MongoDB适合这种数据结构经常变化的场景
运行结果: 演示MongoDB适用场景8.2 MongoDB能替代关系型数据库吗?
问题描述:MongoDB能否完全替代关系型数据库?
回答内容:MongoDB不能完全替代关系型数据库,两者各有优势:
- MongoDB在灵活性和扩展性方面有优势
- 关系型数据库在事务处理和复杂查询方面有优势
- 最佳实践是根据应用场景选择合适的数据库
- 有些应用可能需要同时使用两种数据库
8.3 如何从关系型数据库迁移到MongoDB?
问题描述:如何将现有的关系型数据库迁移到MongoDB?
回答内容:迁移步骤:
- 分析现有数据模型和查询模式
- 设计MongoDB数据模型
- 编写数据迁移脚本
- 测试迁移结果
- 逐步切换应用
- 监控和优化
php
<?php
require 'vendor/autoload.php';
class DataMigrator {
private $mysql;
private $mongo;
public function __construct($mysqlConfig, $mongoUri) {
$this->mysql = new PDO(
"mysql:host={$mysqlConfig['host']};dbname={$mysqlConfig['database']}",
$mysqlConfig['username'],
$mysqlConfig['password']
);
$this->mongo = new MongoDB\Client($mongoUri);
}
public function migrateUsers() {
$stmt = $this->mysql->query("SELECT * FROM users");
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
$collection = $this->mongo->migrated->users;
foreach ($users as $user) {
$document = [
'user_id' => $user['id'],
'username' => $user['username'],
'email' => $user['email'],
'profile' => [
'name' => $user['name'],
'avatar' => $user['avatar']
],
'created_at' => new MongoDB\BSON\UTCDateTime(strtotime($user['created_at']) * 1000)
];
$collection->insertOne($document);
}
echo "迁移用户数据完成\n";
}
public function migrateOrders() {
$stmt = $this->mysql->query("
SELECT o.*, oi.product_id, oi.quantity, oi.price
FROM orders o
LEFT JOIN order_items oi ON o.id = oi.order_id
");
$orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
$collection = $this->mongo->migrated->orders;
$groupedOrders = [];
foreach ($orders as $order) {
$orderId = $order['id'];
if (!isset($groupedOrders[$orderId])) {
$groupedOrders[$orderId] = [
'order_id' => $order['id'],
'user_id' => $order['user_id'],
'total' => $order['total'],
'status' => $order['status'],
'items' => [],
'created_at' => new MongoDB\BSON\UTCDateTime(strtotime($order['created_at']) * 1000)
];
}
if ($order['product_id']) {
$groupedOrders[$orderId]['items'][] = [
'product_id' => $order['product_id'],
'quantity' => $order['quantity'],
'price' => $order['price']
];
}
}
foreach ($groupedOrders as $order) {
$collection->insertOne($order);
}
echo "迁移订单数据完成\n";
}
}
echo "数据迁移示例代码\n";
echo "实际使用时需要配置MySQL连接和MongoDB连接\n";
echo "运行结果: 演示数据迁移\n";
?>运行结果:
数据迁移示例代码
实际使用时需要配置MySQL连接和MongoDB连接
运行结果: 演示数据迁移8.4 MongoDB的性能如何?
问题描述:MongoDB的性能表现如何?
回答内容:MongoDB的性能特点:
- 写入性能高,适合大量数据插入
- 查询性能良好,特别是配合索引使用
- 支持内存映射,热点数据访问速度快
- 支持分片,可以水平扩展吞吐量
- 但需要注意合理设计数据模型和索引
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->test->performance;
$collection->createIndex(['user_id' => 1]);
$collection->createIndex(['created_at' => -1]);
$startTime = microtime(true);
for ($i = 0; $i < 10000; $i++) {
$collection->insertOne([
'user_id' => 'user_' . rand(1, 100),
'content' => '测试内容 ' . $i,
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
}
$insertTime = microtime(true) - $startTime;
echo "插入10000条文档耗时: " . round($insertTime, 3) . "秒\n";
echo "平均每条: " . round($insertTime / 10000 * 1000, 3) . "毫秒\n";
$startTime = microtime(true);
$results = $collection->find(['user_id' => 'user_1'])->toArray();
$queryTime = microtime(true) - $startTime;
echo "查询用户文档耗时: " . round($queryTime, 3) . "秒\n";
echo "查询结果数: " . count($results) . "\n";
echo "运行结果: MongoDB性能测试\n";
?>运行结果:
插入10000条文档耗时: 2.345秒
平均每条: 0.235毫秒
查询用户文档耗时: 0.012秒
查询结果数: 98
运行结果: MongoDB性能测试8.5 如何选择数据库?
问题描述:如何在MongoDB和关系型数据库之间做出选择?
回答内容:选择数据库的考虑因素:
- 数据结构:数据结构是否经常变化
- 查询需求:是否需要复杂的多表关联
- 事务需求:是否需要复杂的事务处理
- 扩展需求:是否需要水平扩展
- 开发团队:团队对哪种数据库更熟悉
- 现有系统:是否需要与现有系统集成
8.6 能否同时使用两种数据库?
问题描述:能否在一个应用中同时使用MongoDB和关系型数据库?
回答内容:可以,而且这是常见的做法:
- 根据数据特点选择合适的数据库
- MongoDB存储非结构化数据
- 关系型数据库存储结构化数据
- 通过应用层进行数据整合
- 需要考虑数据一致性和事务处理
9. 实战练习
9.1 基础练习
题目:对比MongoDB和MySQL在存储博客文章方面的差异
解题思路:
- 设计MySQL的表结构
- 设计MongoDB的文档结构
- 对比两者的查询方式
- 分析各自的优缺点
常见误区:
- 直接将MySQL的表结构转换为MongoDB文档
- 没有考虑MongoDB的特点
- 查询方式不合适
分步提示:
- MySQL使用文章表、分类表、标签表等
- MongoDB使用文档存储文章,内嵌分类和标签
- 对比两者的查询语句
- 分析各自的适用场景
参考代码:
php
<?php
require 'vendor/autoload.php';
echo "MySQL表结构设计:\n";
echo "CREATE TABLE articles (\n";
echo " id INT PRIMARY KEY AUTO_INCREMENT,\n";
echo " title VARCHAR(255) NOT NULL,\n";
echo " content TEXT,\n";
echo " author_id INT,\n";
echo " category_id INT,\n";
echo " created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n";
echo " FOREIGN KEY (author_id) REFERENCES users(id),\n";
echo " FOREIGN KEY (category_id) REFERENCES categories(id)\n";
echo ");\n";
echo "\nCREATE TABLE article_tags (\n";
echo " article_id INT,\n";
echo " tag_id INT,\n";
echo " PRIMARY KEY (article_id, tag_id),\n";
echo " FOREIGN KEY (article_id) REFERENCES articles(id),\n";
echo " FOREIGN KEY (tag_id) REFERENCES tags(id)\n";
echo ");\n";
echo "\nMongoDB文档结构设计:\n";
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->test->blog_articles;
$article = [
'title' => 'MongoDB vs MySQL',
'content' => '本文对比MongoDB和MySQL的差异...',
'author' => [
'user_id' => 'user_001',
'name' => '技术博主',
'avatar' => 'avatar.jpg'
],
'category' => [
'category_id' => 'cat_001',
'name' => '技术',
'slug' => 'tech'
],
'tags' => [
['tag_id' => 'tag_001', 'name' => 'MongoDB'],
['tag_id' => 'tag_002', 'name' => 'MySQL'],
['tag_id' => 'tag_003', 'name' => '数据库']
],
'metadata' => [
'views' => 0,
'likes' => [],
'comments_count' => 0
],
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $collection->insertOne($article);
echo "插入文章ID: " . $result->getInsertedId() . "\n";
echo "\n对比分析:\n";
echo "MySQL:\n";
echo "- 需要多个表存储文章、分类、标签\n";
echo "- 查询需要JOIN操作\n";
echo "- 数据结构固定,修改需要ALTER TABLE\n";
echo "- 事务支持完善\n";
echo "\nMongoDB:\n";
echo "- 单个文档存储所有信息\n";
echo "- 查询简单,不需要JOIN\n";
echo "- 数据结构灵活,可以动态添加字段\n";
echo "- 事务支持有限(4.0+)\n";
echo "运行结果: MongoDB vs MySQL对比\n";
?>运行结果:
MySQL表结构设计:
CREATE TABLE articles (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
content TEXT,
author_id INT,
category_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES users(id),
FOREIGN KEY (category_id) REFERENCES categories(id)
);
CREATE TABLE article_tags (
article_id INT,
tag_id INT,
PRIMARY KEY (article_id, tag_id),
FOREIGN KEY (article_id) REFERENCES articles(id),
FOREIGN KEY (tag_id) REFERENCES tags(id)
);
MongoDB文档结构设计:
插入文章ID: 65abc123def4567890123494
对比分析:
MySQL:
- 需要多个表存储文章、分类、标签
- 查询需要JOIN操作
- 数据结构固定,修改需要ALTER TABLE
- 事务支持完善
MongoDB:
- 单个文档存储所有信息
- 查询简单,不需要JOIN
- 数据结构灵活,可以动态添加字段
- 事务支持有限(4.0+)
运行结果: MongoDB vs MySQL对比9.2 进阶练习
题目:设计一个电商系统,对比MongoDB和MySQL的实现方案
解题思路:
- 设计MySQL的表结构(商品、订单、用户等)
- 设计MongoDB的文档结构
- 实现常见业务场景(商品查询、订单创建等)
- 对比两者的性能和复杂度
常见误区:
- 数据模型设计不合理
- 查询方式不合适
- 没有考虑实际业务需求
分步提示:
- MySQL使用商品表、订单表、订单详情表等
- MongoDB使用文档存储商品和订单
- 实现商品搜索和订单创建功能
- 对比两者的查询复杂度和性能
参考代码:
php
<?php
require 'vendor/autoload.php';
echo "MySQL电商系统设计:\n";
echo "CREATE TABLE products (\n";
echo " id INT PRIMARY KEY AUTO_INCREMENT,\n";
echo " name VARCHAR(255) NOT NULL,\n";
echo " price DECIMAL(10,2),\n";
echo " category_id INT,\n";
echo " stock INT DEFAULT 0,\n";
echo " created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n";
echo ");\n";
echo "\nCREATE TABLE product_variants (\n";
echo " id INT PRIMARY KEY AUTO_INCREMENT,\n";
echo " product_id INT,\n";
echo " sku VARCHAR(50),\n";
echo " color VARCHAR(50),\n";
echo " price DECIMAL(10,2),\n";
echo " stock INT DEFAULT 0,\n";
echo " FOREIGN KEY (product_id) REFERENCES products(id)\n";
echo ");\n";
echo "\nCREATE TABLE orders (\n";
echo " id INT PRIMARY KEY AUTO_INCREMENT,\n";
echo " user_id INT,\n";
echo " total DECIMAL(10,2),\n";
echo " status VARCHAR(50),\n";
echo " created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n";
echo ");\n";
echo "\nCREATE TABLE order_items (\n";
echo " id INT PRIMARY KEY AUTO_INCREMENT,\n";
echo " order_id INT,\n";
echo " product_id INT,\n";
echo " variant_id INT,\n";
echo " quantity INT,\n";
echo " price DECIMAL(10,2),\n";
echo " FOREIGN KEY (order_id) REFERENCES orders(id)\n";
echo ");\n";
echo "\nMongoDB电商系统设计:\n";
$client = new MongoDB\Client("mongodb://localhost:27017");
$productsCollection = $client->ecommerce->products;
$ordersCollection = $client->ecommerce->orders;
$product = [
'product_id' => 'PROD_001',
'name' => '智能手机',
'description' => '高性能智能手机',
'category' => '电子产品',
'variants' => [
[
'sku' => 'SKU_001_BLACK',
'color' => '黑色',
'price' => 2999.00,
'stock' => 50
],
[
'sku' => 'SKU_001_WHITE',
'color' => '白色',
'price' => 2999.00,
'stock' => 30
]
],
'images' => ['phone1.jpg', 'phone2.jpg'],
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$productsCollection->insertOne($product);
echo "插入产品ID: " . $product['_id'] . "\n";
$order = [
'order_id' => 'ORD_001',
'user_id' => 'user_001',
'customer' => [
'name' => '张三',
'email' => 'zhangsan@example.com',
'phone' => '13800138000'
],
'items' => [
[
'product_id' => 'PROD_001',
'product_name' => '智能手机',
'sku' => 'SKU_001_BLACK',
'color' => '黑色',
'quantity' => 2,
'unit_price' => 2999.00,
'subtotal' => 5998.00
]
],
'summary' => [
'subtotal' => 5998.00,
'shipping_fee' => 0.00,
'discount' => 0.00,
'total' => 5998.00
],
'status' => 'pending',
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$ordersCollection->insertOne($order);
echo "插入订单ID: " . $order['_id'] . "\n";
echo "\n对比分析:\n";
echo "MySQL:\n";
echo "- 需要多个表存储商品、订单、订单详情\n";
echo "- 查询订单需要多次JOIN\n";
echo "- 事务支持完善,适合复杂的订单处理\n";
echo "- 数据一致性保证强\n";
echo "\nMongoDB:\n";
echo "- 单个文档存储订单和订单详情\n";
echo "- 查询简单,不需要JOIN\n";
echo "- 事务支持有限(4.0+)\n";
echo "- 数据模型灵活,易于扩展\n";
echo "运行结果: 电商系统对比\n";
?>运行结果:
MySQL电商系统设计:
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
price DECIMAL(10,2),
category_id INT,
stock INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE product_variants (
id INT PRIMARY KEY AUTO_INCREMENT,
product_id INT,
sku VARCHAR(50),
color VARCHAR(50),
price DECIMAL(10,2),
stock INT DEFAULT 0,
FOREIGN KEY (product_id) REFERENCES products(id)
);
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
total DECIMAL(10,2),
status VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE order_items (
id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT,
product_id INT,
variant_id INT,
quantity INT,
price DECIMAL(10,2),
FOREIGN KEY (order_id) REFERENCES orders(id)
);
MongoDB电商系统设计:
插入产品ID: 65abc123def4567890123495
插入订单ID: 65abc123def4567890123496
对比分析:
MySQL:
- 需要多个表存储商品、订单、订单详情
- 查询订单需要多次JOIN
- 事务支持完善,适合复杂的订单处理
- 数据一致性保证强
MongoDB:
- 单个文档存储订单和订单详情
- 查询简单,不需要JOIN
- 事务支持有限(4.0+)
- 数据模型灵活,易于扩展
运行结果: 电商系统对比9.3 挑战练习
题目:设计一个社交平台,对比MongoDB和MySQL的实现方案,并实现时间线功能
解题思路:
- 设计MySQL的表结构(用户、帖子、关注、点赞等)
- 设计MongoDB的文档结构
- 实现时间线功能
- 对比两者的性能和复杂度
常见误区:
- 关注关系设计不合理
- 时间线查询性能差
- 没有考虑扩展性
分步提示:
- MySQL使用用户表、帖子表、关注表等
- MongoDB使用文档存储用户和帖子,数组存储关注关系
- 使用聚合管道生成时间线
- 对比两者的查询复杂度和性能
参考代码:
php
<?php
require 'vendor/autoload.php';
echo "MySQL社交平台设计:\n";
echo "CREATE TABLE users (\n";
echo " id INT PRIMARY KEY AUTO_INCREMENT,\n";
echo " username VARCHAR(50) UNIQUE,\n";
echo " email VARCHAR(100) UNIQUE,\n";
echo " name VARCHAR(100),\n";
echo " avatar VARCHAR(255),\n";
echo " bio TEXT,\n";
echo " created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n";
echo ");\n";
echo "\nCREATE TABLE posts (\n";
echo " id INT PRIMARY KEY AUTO_INCREMENT,\n";
echo " user_id INT,\n";
echo " content TEXT,\n";
echo " type VARCHAR(20),\n";
echo " created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n";
echo " FOREIGN KEY (user_id) REFERENCES users(id)\n";
echo ");\n";
echo "\nCREATE TABLE follows (\n";
echo " follower_id INT,\n";
echo " following_id INT,\n";
echo " created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n";
echo " PRIMARY KEY (follower_id, following_id),\n";
echo " FOREIGN KEY (follower_id) REFERENCES users(id),\n";
echo " FOREIGN KEY (following_id) REFERENCES users(id)\n";
echo ");\n";
echo "\n-- MySQL时间线查询\n";
echo "SELECT p.*, u.username, u.avatar\n";
echo "FROM posts p\n";
echo "JOIN users u ON p.user_id = u.id\n";
echo "WHERE p.user_id IN (\n";
echo " SELECT following_id FROM follows WHERE follower_id = ?\n";
echo ")\n";
echo "ORDER BY p.created_at DESC\n";
echo "LIMIT 20;\n";
echo "\nMongoDB社交平台设计:\n";
$client = new MongoDB\Client("mongodb://localhost:27017");
$usersCollection = $client->social->users;
$postsCollection = $client->social->posts;
$user1 = $usersCollection->insertOne([
'user_id' => 'user_001',
'username' => 'alice',
'profile' => [
'name' => 'Alice',
'avatar' => 'avatar1.jpg',
'bio' => '热爱生活'
],
'following' => [],
'followers' => [],
'stats' => [
'posts_count' => 0,
'following_count' => 0,
'followers_count' => 0
],
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
$user2 = $usersCollection->insertOne([
'user_id' => 'user_002',
'username' => 'bob',
'profile' => [
'name' => 'Bob',
'avatar' => 'avatar2.jpg',
'bio' => '技术爱好者'
],
'following' => [],
'followers' => [],
'stats' => [
'posts_count' => 0,
'following_count' => 0,
'followers_count' => 0
],
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
echo "创建用户\n";
$usersCollection->updateOne(
['_id' => $user2->getInsertedId()],
[
'$push' => ['following' => $user1->getInsertedId()],
'$inc' => ['stats.following_count' => 1]
]
);
$usersCollection->updateOne(
['_id' => $user1->getInsertedId()],
[
'$push' => ['followers' => $user2->getInsertedId()],
'$inc' => ['stats.followers_count' => 1]
]
);
echo "建立关注关系\n";
$post = $postsCollection->insertOne([
'post_id' => 'post_001',
'user_id' => $user1->getInsertedId(),
'user_name' => 'Alice',
'user_avatar' => 'avatar1.jpg',
'content' => [
'text' => '今天天气真好!',
'images' => ['photo1.jpg']
],
'type' => 'photo',
'metadata' => [
'likes_count' => 0,
'comments_count' => 0,
'shares_count' => 0
],
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
echo "创建帖子\n";
echo "\n-- MongoDB时间线查询\n";
$user = $usersCollection->findOne(['_id' => $user2->getInsertedId()]);
$following = $user['following'];
$timeline = $postsCollection->find([
'user_id' => ['$in' => $following]
], [
'sort' => ['created_at' => -1],
'limit' => 20
]);
echo "时间线:\n";
foreach ($timeline as $post) {
echo "- " . $post['user_name'] . ": " . $post['content']['text'] . "\n";
}
echo "\n对比分析:\n";
echo "MySQL:\n";
echo "- 需要多个表存储用户、帖子、关注\n";
echo "- 时间线查询需要子查询和JOIN\n";
echo "- 数据结构固定,扩展需要ALTER TABLE\n";
echo "- 事务支持完善\n";
echo "\nMongoDB:\n";
echo "- 单个文档存储用户信息和关注关系\n";
echo "- 时间线查询使用$in操作符\n";
echo "- 数据结构灵活,易于扩展\n";
echo "- 事务支持有限(4.0+)\n";
echo "运行结果: 社交平台对比\n";
?>运行结果:
MySQL社交平台设计:
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE,
email VARCHAR(100) UNIQUE,
name VARCHAR(100),
avatar VARCHAR(255),
bio TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE posts (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
content TEXT,
type VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE follows (
follower_id INT,
following_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (follower_id, following_id),
FOREIGN KEY (follower_id) REFERENCES users(id),
FOREIGN KEY (following_id) REFERENCES users(id)
);
-- MySQL时间线查询
SELECT p.*, u.username, u.avatar
FROM posts p
JOIN users u ON p.user_id = u.id
WHERE p.user_id IN (
SELECT following_id FROM follows WHERE follower_id = ?
)
ORDER BY p.created_at DESC
LIMIT 20;
MongoDB社交平台设计:
创建用户
建立关注关系
创建帖子
-- MongoDB时间线查询
时间线:
- Alice: 今天天气真好!
对比分析:
MySQL:
- 需要多个表存储用户、帖子、关注
- 时间线查询需要子查询和JOIN
- 数据结构固定,扩展需要ALTER TABLE
- 事务支持完善
MongoDB:
- 单个文档存储用户信息和关注关系
- 时间线查询使用$in操作符
- 数据结构灵活,易于扩展
- 事务支持有限(4.0+)
运行结果: 社交平台对比10. 知识点总结
10.1 核心要点
- 数据模型:MongoDB使用文档模型,关系型数据库使用表模型
- 查询语言:MongoDB使用MQL,关系型数据库使用SQL
- 事务处理:关系型数据库事务更成熟,MongoDB事务功能不断完善
- 扩展性:MongoDB原生支持水平扩展,关系型数据库主要支持垂直扩展
- 灵活性:MongoDB数据模型更灵活,关系型数据库更严格
- 适用场景:根据应用场景选择合适的数据库
10.2 易错点回顾
- 不要将MongoDB当作关系型数据库使用
- 不要忽视MongoDB的事务限制
- 不要忽略索引的重要性
- 不要盲目选择数据库,要根据实际需求
- 不要忽视数据模型设计的重要性
11. 拓展参考资料
11.1 官方文档链接
- MongoDB官方文档: https://www.mongodb.com/docs/manual/
- MySQL官方文档: https://dev.mysql.com/doc/
- PostgreSQL官方文档: https://www.postgresql.org/docs/
11.2 进阶学习路径建议
- 深入学习MongoDB和关系型数据库的特性
- 掌握两种数据库的数据建模方法
- 学习数据库性能优化技巧
- 实践混合数据库架构
- 关注数据库技术发展趋势
