简介一份面向大数据课程学习者的实验报告资源包围绕MySQL、HBase、Redis、MongoDB四种数据库的操作对比展开适合正在完成NoSQL与关系数据库实验的学生作为参考模板。压缩包内含1个docx格式的完整实验报告大小约1.54MB报告基于LinuxUbuntu 16.04平台涉及Hadoop 2.7.1、MySQL 5.7.15、HBase 1.1.2、Redis 3.0.6、MongoDB 3.2.6及JDK 1.7等版本环境详细记录了Student表的建表、插入、查询、更新等SQL语句并给出经Eclipse调试通过的JDBC、HBase、Jedis等Java API示例代码。实验内容同时覆盖四种数据库的Shell命令和Java编程接口帮助读者理解关系型数据模型与NoSQL数据模型的差异并能在本地反复运行以巩固知识点。通过对比四个数据库的建表、增删改查等操作可直观看到SQL语法与NoSQL API的异同为后续大数据项目选型打下基础。目前已有3500余人学习该报告实用性和参考价值较高适合大数据、数据仓库方向学生作为课程实验参考。1. 实验四NoSQL和关系数据库的操作比较不先跑完这轮对比别急着选型很多团队在数据库选型上吵到最后靠的不是各种排名而是一份能复现的实测报告。实验四这个题目听起来像课程作业实际上就是把增删改查、批量写入、条件查询、事务提交这些日常高频操作分别在以 MySQL 为代表的关系数据库和以 MongoDB 为代表的 NoSQL 里跑一遍把差异量化成操作时间、语法成本和踩坑数量。它能帮你回答一个非常现实的问题业务里哪些部分适合继续用表结构哪些部分交给文档模型更顺手。这个实验适合两类人。一类是被分配了“对比选型”任务的开发或运维需要一份能在汇报里讲清楚的操作数据另一类是想从 MySQL 跳去写 NoSQL 的人想搞明白 mongosh 和 SQL 之间每个命令是怎么对应过去的。我真正常年做这类对比后最大的体会是结论落点不在“谁快谁慢”而在你反复操作过程中记住的那些系统性差异和边界条件。2. 搭建可复现的对比环境用 Docker Compose 同时拉起 MySQL 8 与 MongoDB 62.1 先搞懂模型差异表结构 vs 文档嵌套实验的第一步不是装环境而是把一个业务分别建模。关系数据库强调“先设计后写入”用户表、订单表、订单明细表用主外键关联起来写入订单前必须把三张表定义好任何一行数据不满足约束数据库直接拒绝。这种 schema-on-write 的模式把完整性校验放在写入前换来的是高度一致的查询视图。MongoDB 走的是另一条路一条订单文档可以内嵌用户信息和商品明细数组。你不用提前建集合甚至不用提前定义字段文档写入后集合自然存在。这个特性让它在快速迭代阶段特别舒服但在做统计报表时会让人头疼因为你需要用聚合框架把嵌套数组拆开。很多从 MySQL 转过来的人第一次用$unwind时的崩溃感就来自这里。用一段最小示例说明同样的订单数据两种建模长什么样-- MySQL订单主表 CREATE TABLE orders ( order_id BIGINT PRIMARY KEY AUTO_INCREMENT, user_name VARCHAR(50), status VARCHAR(20), amount DECIMAL(10,2), created_at DATETIME );// MongoDB同一条订单带嵌套用户信息和商品明细 db.orders.insertOne({ order_id: 10001, user: { name: 张三, level: vip }, items: [ { sku: A001, qty: 2, price: 19.90 }, { sku: B002, qty: 1, price: 99.00 } ], status: pending, created_at: new Date() });这两段代码一眼就能看出差异MySQL 定义VARCHAR、DECIMAL、DATETIME所有字段必须在建表时想清楚MongoDB 直接把对象嵌套进文档user和items都是结构化的值。关系型数据库把完整性交给数据库NoSQL 把灵活性留给应用层这个选择会直接影响后面所有操作对比的结果所以实验报告里最好一开始就写清这个建模前提。2.2 Docker Compose 拉起最小实例一条命令得到两个数据库本地实验不需要在物理机里装两套数据库常见做法是用 Docker Compose 同时起 MySQL 8 和 MongoDB 6。两个容器互不干扰环境统一清数据重建也快。Compose 文件我一般这样写services: mysql: image: mysql:8.0 container_name: lab_mysql ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: lab TZ: Asia/Shanghai command: --default-time-zone08:00 volumes: - mysql_data:/var/lib/mysql mongodb: image: mongo:6.0 container_name: lab_mongo ports: - 27017:27017 environment: MONGO_INITDB_DATABASE: lab volumes: - mongo_data:/data/db volumes: mysql_data: mongo_data:这个文件里的关键点有三个command: --default-time-zone08:00是为了让 MySQL 和 MongoDB 的时间口径尽量一致后面做时间查询对比时少一个变量MYSQL_DATABASE和MONGO_INITDB_DATABASE是官方镜像初始化库用的容器第一次启动时自动建lab库volumes把数据落到宿主机删容器不丢数据实验翻车了还能有后悔药。启动后先验证服务起来了docker compose up -d docker exec -it lab_mysql mysql -uroot -proot123 lab -e SELECT VERSION(); docker exec -it lab_mongo mongosh --quiet --eval db.version()docker exec进容器执行命令这时候 MySQL 客户端可能需要等十几秒才就绪因为 8.0 首次初始化要跑一堆系统表。连接串口令直接写在命令行里只适合本地实验别搬到生产脚本。进入lab库后先给两个环境建好同一张订单表。MySQL 用第 2.1 节的建表语句MongoDB 这边不需要显式建集合插入第一条文档时集合自动出现。为了后面操作对比时不至于在空库上较劲先各插入一批状态不同、金额不同的订单数据。可以参考这个做法给 MySQL 用INSERT ... VALUES一次插 5 条给 MongoDB 用insertMany一次插 5 条保证两端拿到的数据分布一致。这里给出两张表的概念映射后续查语法时对照着看概念关系数据库MongoDB库databasedatabase表tablecollection行rowdocument列columnfield主键PRIMARY KEY_id索引indexindex查询接口SQLfind / aggregate这张对照表做完实验后可以放进汇报 PPT是快手能用的图。接下来所有章节的操作都建立在“同一个订单业务、两种建模方式”的基础上脱离了业务谈数据库性能没有意义。3. 插入与查询的操作对照从 INSERT 到 insertMany从 SELECT 到 find3.1 单条与批量插入NoSQL 的写入模型为什么在这轮占优势插入操作的对比是实验里最直观的部分。关系数据库每次INSERT都要走 SQL 解析、权限检查、InnoDB 事务日志、索引维护如果表上有多个二级索引写入代价会被继续放大。MongoDB 在默认配置下写入先落内存和 journal文档结构简单也没有外键约束检查所以很多实验里单条插入的数据NoSQL 看起来“更快”。但这话必须加前提如果 MongoDB 开启同步刷盘的单写模式或者把writeConcern设为 majority速度会明显下降。实验结论里想写“NoSQL 插入更快”最好同时标上写入级别否则这个结论就是悬空的。先看最基础的单条和批量插入-- 单条插入 INSERT INTO orders (user_name, status, amount, created_at) VALUES (zhangsan, pending, 199.00, NOW()); -- 单条 SQL 批量插入 5 行 INSERT INTO orders (user_name, status, amount, created_at) VALUES (user_1, pending, 199.00, NOW()), (user_2, paid, 599.00, NOW()), (user_3, cancelled, 89.00, NOW()), (user_4, pending, 299.00, NOW()), (user_5, paid, 129.00, NOW());// MongoDB 单条插入 db.orders.insertOne({ order_id: NumberLong(100001), user_name: zhangsan, status: pending, amount: 199.00, created_at: new Date() }); // MongoDB 批量插入 5 条 db.orders.insertMany([ { order_id: 100002, user_name: user_1, status: pending, amount: 199.00, created_at: new Date() }, { order_id: 100003, user_name: user_2, status: paid, amount: 599.00, created_at: new Date() }, { order_id: 100004, user_name: user_3, status: cancelled, amount: 89.00, created_at: new Date() }, { order_id: 100005, user_name: user_4, status: pending, amount: 299.00, created_at: new Date() }, { order_id: 100006, user_name: user_5, status: paid, amount: 129.00, created_at: new Date() } ]);注意 MongoDB 批量插入的默认参数是ordered: true也就是按顺序插入中间某一条报错后面的文档会停止写入。想追求高吞吐实验里可以显式传{ ordered: false }让数据库尽可能多地写入最后统一返回失败项。MySQL 这边对应的是max_allowed_packet参数单条 SQL 的值列表越长网络包可能超过阈值。批量插入的差异在数据量上来后才会体现。我用 Python 脚本往两端各插 10 万条时MySQL 如果逐条INSERT会慢到让人怀疑机器坏了而 MongoDB 用insertMany几秒就能写完。但这不一定说明 NoSQL 的磁盘更快只是它的驱动层天然鼓励批量接口而 MySQL 的常见误区是没走 multi-row insert。所以做实验时一定要分别测“逐条插入”和“批量插入”这两个数字放在一起才是能说服人的结论。3.2 条件查询、排序与分页SQL 与 MQL 的逐词对应查询部分的对比最有学习价值因为 MQLMongoDB Query Language和 SQL 的思维方式没有想象中隔得那么远。一个典型场景查询已支付且金额大于 100 的订单按时间倒序取第 3 页每页 20 条。SELECT order_id, user_name, amount, created_at FROM orders WHERE status paid AND amount 100 ORDER BY created_at DESC LIMIT 20 OFFSET 40;db.orders.find( { status: paid, amount: { $gt: 100 } }, { order_id: 1, user_name: 1, amount: 1, created_at: 1, _id: 0 } ) .sort({ created_at: -1 }) .skip(40) .limit(20);这段 MongoDB 查询里find的第一个参数对应WHERE条件字段按键值对传第二个参数是投影1表示返回该字段_id: 0表示去掉默认的_id相当于SELECT后面的字段列表。$gt是大于对应 SQL 里的。排序和分页直接叫sort、skip、limit大部分开发者看一眼就能猜对。下面这张映射表是实验笔记里的固定内容SQL 子句MongoDB 方法/操作符WHEREfind 的第一个参数AND多个条件字段并列OR$or: [{...}, {...}]IN$in: [值1, 值2]LIKE %x%$regex: xSELECT 字段find 的第二个参数ORDER BYsort({ field: 1 或 -1 })LIMIT / OFFSETlimit(n) / skip(n)COUNTcountDocuments({...})数据量小的时候不带索引的查询两端都能秒回很多实验做到这里就停了这是最可惜的。完整的对比一定要在这一步建索引否则后面数据量一大COLLSCAN 和全表扫描会让你以为数据库坏了。CREATE INDEX idx_status_amount ON orders(status, amount); EXPLAIN SELECT order_id, user_name, amount, created_at FROM orders WHERE status paid AND amount 100 ORDER BY created_at DESC LIMIT 20 OFFSET 40;db.orders.createIndex({ status: 1, amount: 1 }); db.orders.find( { status: paid, amount: { $gt: 100 } } ).explain(executionStats);MySQL 的EXPLAIN看type字段从ALL变成ref或range说明索引生效了MongoDB 的explain(executionStats)看stage从COLLSCAN变成IXSCAN执行计划里还能看到totalDocsExamined和totalKeysExamined的对比。这里有个血泪经验做对比实验时一定要把执行计划截图存底因为《NoSQL 和关系数据库操作比较》这份报告里“有没有走索引”才是查询性能差异的真正来源而不是数据库类型本身。4. 更新、删除与事务NoSQL 和关系数据库在写路径上的分水岭4.1 UPDATE 与 updateOne操作符模型与脏数据风险更新操作在两边语法上也能对应但背后理念开始分叉。MySQL 的UPDATE直接给字段赋新值MongoDB 引入了$set、$inc这类更新操作符本质是“告诉数据库怎么改”而不是“把新值写进去”。这个习惯如果没转过来最常见的翻车写法是把整个文档读出来改完再写回去不仅慢并发下还容易丢更新。先看单文档更新对比UPDATE orders SET status paid, paid_at NOW() WHERE order_id 100002;db.orders.updateOne( { order_id: 100002 }, { $set: { status: paid, paid_at: new Date() } } );updateOne的第一个参数是过滤条件第二个参数是更新动作。$set只更新指定字段不碰其他字段这是和“整个文档替换”最关键的区别。如果只想让金额增加用$inc: { amount: 10 }等价于UPDATE ... SET amount amount 10。MongoDB 还有一个更体贴的upsert: true选项条件匹配不到就插入新文档MySQL 里要靠INSERT ... ON DUPLICATE KEY UPDATE才能做到语法复杂度高不少。删除操作也有同样的对应关系DELETE FROM orders WHERE order_id 100003;db.orders.deleteOne({ order_id: 100003 });清空数据时要注意语义差异MySQL 的TRUNCATE TABLE orders会重置自增主键并释放表空间MongoDB 的db.orders.deleteMany({})只是逐条删除文档自增_id不会重置集合的物理文件也不会缩小除非用db.orders.drop()直接删集合再重建。实验里如果反复插入、清空、再插入你会发现 MySQL 的order_id总是从 1 开始MongoDB 的order_id还在继续涨这个现象本身就值得写进报告。4.2 事务与原子性取舍ACID 默认与可选之间的成本关系数据库对事务的支持是默认全开的这也是它处理账务、库存等强一致业务时的底气。实验里随手写一段转账就能体验 ACID 的效果START TRANSACTION; UPDATE accounts SET balance balance - 100 WHERE account_id 1; UPDATE accounts SET balance balance 100 WHERE account_id 2; COMMIT;这段代码在 MySQL 里不用额外配置就能跑事务中途任何一个UPDATE失败ROLLBACK都能把两边回滚到初始值。MongoDB 的单文档操作天然具有原子性但跨文档事务不是默认能力需要副本集环境支持。默认的docker run mongo是单节点实例直接跑事务会报错必须先初始化副本集。MongoDB 多文档事务的长相是这样的const session db.getMongo().startSession(); session.startTransaction(); const orders session.getDatabase(lab).orders; orders.updateOne( { order_id: 100001 }, { $set: { status: paid } } ); session.commitTransaction(); session.endSession();要在 Docker 环境里启用副本集Compose 里 MongoDB 服务的启动命令要加上--replSet rs0容器起来后再执行一次rs.initiate()。这一步配置很多人会漏但恰恰是实验报告里最好的素材NoSQL 不是不能做事务而是事务的部署前提和性能成本比关系数据库高业务要为此买单。我一般的建议是核心账务、财务对账、库存扣减这类强一致场景继续放在关系数据库会话、日志、商品资料这类允许最终一致、结构频繁变化的数据放 NoSQL。实验做到事务这一轮你对这句话的理解会比看文章深刻得多。4.3 数据导入导出与字段类型映射实验数据怎么搬才不丢实验中经常要在 MySQL 和 MongoDB 之间搬运数据做对比比如同一份测试数据先导入 MySQL 跑一遍查询再导入 MongoDB 跑一遍。两边常用工具命令如下# MySQL 导出 mysqldump -uroot -proot123 lab orders orders.sql # 用 CSV 格式导出表 mysql -uroot -proot123 lab -e SELECT * FROM orders -B orders.tsv # MongoDB 导出为 JSON mongoexport --db lab --collection orders --out orders.json # MongoDB 导入 JSON mongoimport --db lab --collection orders --file orders.jsonmongoexport导出的 JSON 是数组结构mongoimport可以原样导回。但直接把 MySQL 的DATETIME放进 Mongo 的Date字段时你会发现导出文件里带$date包裹用脚本处理时容易踩坑。类型映射最好单独开一页记录MySQL 类型MongoDB 类型实验注意事项BIGINTNumberLong64位整型JSON 里超出 JS 安全整数会失真DECIMALDecimal128金额精度敏感别默认用 doubleDATETIMEDate时区口径不同默认 UTCVARCHARString排序规则不互通TINYINT(1) / BOOLBoolean语义相同类型名不同如果金额用 double 存可能出现 0.1 0.2 这类浮点误差问题对账场景直接翻车。DB 侧的数据类型映射是这类对比实验里最容易被忽略却最容易出事故的部分提前列这个表能省不少排查时间。5. 避坑手记NoSQL 与关系数据库对比实验里最常见的 5 个翻车点5.1 字段名大小写不一致导致 MongoDB 查不到数据现象同样的条件MySQL 能查到MongoDB 返回空集合。原因MongoDB 文档没有 schema 强约束字段名大小写敏感。插入时写的是Status查询时写status两边是不同字段。解决查询前先执行一次db.orders.findOne()看实际字段名更规范的做法是在应用入口统一字段命名比如全小写下划线风格并写一段插入前的字段校验逻辑。5.2 时区差 8 小时导致时间查询结果对不上现象按created_at查询“昨天的订单”MySQL 和 MongoDB 返回的记录数不一致。原因MongoDB 内部默认以 UTC 存储日期MySQL 的时区取决于容器或系统配置。两端时间口径不一致筛选结果当然不同。解决Compose 里给 MySQL 加TZ: Asia/Shanghai和--default-time-zone08:00MongoDB 侧查询时用ISODate包裹实际时间如果两端都存时间戳Unix 毫秒可以彻底绕开时区问题代价是读数据时多一步转换。5.3 大批量插入时 MySQL 慢到像卡死MongoDB 却秒完现象数据量十万里级别时逐条INSERT循环写入 MySQL 非常慢insertMany写入 MongoDB 很快。原因逐条插入时每条 SQL 都要走完整的解析、事务日志和网络往返MongoDB 驱动层天然支持批量写入网络交互次数少一个数量级。这不能简单归纳为“NoSQL 写入更快”。解决MySQL 侧改 multi-row insert一次INSERT ... VALUES (...),(...)脚本里用executemany批量提交。比较两端性能时必须同时记录“单条循环”和“批量写入”两个数据否则结论不可复现。5.4 建了索引但 EXPLAIN 里还是全表扫描现象数据量大了之后查询变慢明明建过索引看执行计划发现 MySQLtypeALLMongoDBstageCOLLSCAN。原因索引字段顺序没考虑查询条件的等值和范围顺序比如对amount建了单字段索引但查询条件里先过滤status再过滤amount或者查询里对索引列做了函数包裹。解决MySQL 用EXPLAIN看key和rowsMongoDB 用explain(executionStats)看totalDocsExamined把索引建到“等值条件放前面、范围条件放后面”的顺序上来。这个坑在实验里特别适合截图留档因为它是两类数据库结合使用时的共同问题。5.5 TRUNCATE 和 deleteMany 的“清空”不是一回事现象实验反复跑了几轮后MySQL 的自增order_id每次都从 1 开始MongoDB 的order_id却越涨越大集合占用的磁盘空间也没有缩小。原因TRUNCATE TABLE是 DDL直接重建表并重置自增deleteMany({})是逻辑删除只删除文档不重置_id也不回收已分配的存储段。解决想要完全重置实验现场用db.orders.drop()删掉集合再重新插入只想清数据保住集合索引则接受_id继续增长的事实。实验报告里建议明确写“清空方式”否则后续跑性能测试时物理空间碎片会影响写入结果。6. 把对比做成可复现的验证压测脚本与选型结论怎么落笔前面的实验都是命令行手工操作能感受到语法差异但要拿数据说话还得写一段小脚本统一跑相同逻辑。我一般用 Python 的pymysql和pymongo写一个最小压测分别记录时间注意每次脚本运行前先把两端数据清空保证对比起点一致import time import pymysql import pymongo # MySQL 批量插入 1 万条 conn pymysql.connect( host127.0.0.1, userroot, passwordroot123, databaselab ) t0 time.time() cur conn.cursor() rows [ (fuser_{i}, pending, 100 i, time.strftime(%Y-%m-%d %H:%M:%S)) for i in range(10000) ] cur.executemany( INSERT INTO orders (user_name, status, amount, created_at) VALUES (%s, %s, %s, %s), rows ) conn.commit() print(MySQL executemany:, round(time.time() - t0, 3), s) conn.close() # MongoDB 批量插入 1 万条 client pymongo.MongoClient(mongodb://127.0.0.1:27017) db client[lab] t0 time.time() docs [ {user_name: fuser_{i}, status: pending, amount: 100 i, created_at: time.time()} for i in range(10000) ] db.orders.insert_many(docs, orderedFalse) print(MongoDB insert_many:, round(time.time() - t0, 3), s) client.close()这段代码里的关键点是两端都用“批量接口”而不是逐条循环这样比的是数据库自身写入能力而不是运输链路的差异。数据量建议从 1 万条起多跑几轮取中位数或 P95不要用单次耗时下结论。如果测试查询性能还要在脚本里用timeit重复执行同一条件查询并交替打乱顺序避免缓存叠加干扰结果。脚本跑完后把结果落成一张表操作类型、操作方式、MySQL 耗时、MongoDB 耗时、是否走索引。选型结论不要只写“NoSQL 比关系数据库快”而应该写“在批量写入场景下MongoDB 的驱动接口耗时更低在强一致事务场景下MySQL 的默认事务能力更成熟”。我现在做这类对比实验一定先把脚本和数据集准备好再启动数据库实例否则一边敲命令一边补数据最后很难得到能汇报的数字。我也习惯把每条结论都标上操作方式因为脱离写入级别和执行计划谈数据库快慢本质上就是玄学。希望帮到你。本文还有配套的精品资源点击获取