免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

四种数据库操作对比:MySQL/HBase/Redis/MongoDB实战解析

四种数据库操作对比:MySQL/HBase/Redis/MongoDB实战解析 简介本资源是面向大数据初学者与高校计算机专业学生的NoSQL与关系数据库对比实验教学材料聚焦MySQL、HBase、Redis和MongoDB四类数据库的核心差异与实操能力培养。内容覆盖概念辨析、Shell命令操作及Java API编程实践特别适配《大数据基础编程、实验和案例教程》第6章学习需求助力读者建立多模型数据库选型与应用的系统认知。资源为单个1.54MB的Word文档.docx完整呈现实验报告全文含详细环境配置Ubuntu 16.04Hadoop 2.7.1各数据库对应版本、MySQL建表/增删改查SQL示例、Java JDBC代码实现含PreparedStatement封装与异常处理以及HBase、Redis、MongoDB的操作要点提示。目前已有3502人学习下载内容结构清晰、代码可直接复用、注释详尽是开展数据库对比实验与课程作业提交的可靠参考依据。1. 四种数据库在学生数据场景下的操作分层对比不是选“快”或“慢”而是看“结构刚性”与“写入路径”你刚在 HBase Shell 里敲下put Student,2,score:Math,95却发现 MySQL 里一句UPDATE Student SET Math95 WHERE namelisi就能完成同样动作——表面看只是命令不同但背后是四种完全不同的数据建模哲学。本实验不是教你怎么“装 Redis”或“连 MongoDB”而是用同一张学生表Name 三门课成绩作为标尺把 MySQL 的行式强约束、HBase 的列族稀疏存储、Redis 的内存哈希映射、MongoDB 的嵌套文档结构全部拉到同一个操作平面上做显微级比对。它适合两类人一是刚学完 CAP 理论但还没在真实数据流里踩过坑的初学者二是正在为日志聚合系统选型、却卡在“到底该用 HBase 还是 MongoDB 存用户行为序列”的工程师。关键不在于哪个更快而在于当你需要支持“每秒新增 2000 名学生、每人动态增减选修课字段、且要按课程名模糊检索历史成绩”时MySQL 的 ALTER TABLE 会锁表 3 分钟HBase 的scan可以加过滤器实时响应Redis 的HGETALL返回的是完整哈希桶而非投影结果MongoDB 的$regex查询则可能触发全表扫描——这些差异在插入一条scofield记录的 Java 代码里已经埋下了未来扩展性的伏笔。2. 关系型数据库 MySQLACID 保障下的结构化操作与 JDBC 实现细节2.1 表结构定义与 SQL 操作的隐含约束逻辑MySQL 的CREATE TABLE Student (Name varchar(30) NOT NULL, English TINYINT, Math TINYINT, Computer TINYINT)不仅声明了字段类型更强制设定了三重约束NOT NULL意味着 Name 字段不可为空TINYINT限定了成绩取值范围为 -128~127若业务要求满分 150则必须改用SMALLINT而未显式声明主键导致后续UPDATE依赖name字段唯一性——这在真实场景中极易引发误更新如存在两个同名学生。实验中INSERT INTO Student VALUES (zhangsan,69,86,77)成功执行是因为 MySQL 默认启用sql_modeSTRICT_TRANS_TABLES时会校验TINYINT范围但若关闭严格模式插入 150 会被静默截断为 127这种隐式转换正是生产环境数据失真的常见源头。提示实验环境使用 MySQL 5.7.33其默认sql_mode包含ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION。可通过SELECT sql_mode;验证避免因模式差异导致本地测试通过而线上报错。2.2 JDBC 连接配置的关键参数解析与连接池实践实验 Java 代码中jdbc:mysql://localhost:3306/test?useSSLfalseserverTimezoneUTC的 URL 参数需深度拆解useSSLfalse禁用 SSL 加密仅限内网开发环境生产环境必须启用useSSLtrue并配置trustCertificateKeyStoreUrlserverTimezoneUTC强制服务端时区为 UTC避免客户端JVM时区与 MySQL 服务器时区不一致导致TIMESTAMP字段写入偏移如 JVM 设为Asia/Shanghai未设此参数时插入2024-01-01 00:00:00会被转为2023-12-31 16:00:00缺失的characterEncodingutf8mb4参数会导致中文姓名如“张三”存入后变成乱码因 MySQL 5.7 默认utf8实际为utf8mb3无法存储 emoji 或四字节 UTF-8 字符。实际工程中应替换为连接池实现例如 HikariCP 配置HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/test?useSSLfalseserverTimezoneUTCcharacterEncodingutf8mb4); config.setUsername(root); config.setPassword(123456); config.addDataSourceProperty(cachePrepStmts, true); // 开启 PreparedStatement 缓存 config.addDataSourceProperty(prepStmtCacheSize, 250); // 缓存大小 config.addDataSourceProperty(prepStmtCacheSqlLimit, 2048); // 单条 SQL 最大长度 config.setMaximumPoolSize(20); HikariDataSource dataSource new HikariDataSource(config);此配置相比原始DriverManager.getConnection提升了连接复用率避免频繁 TCP 握手开销且cachePrepStmts参数使PreparedStatement在连接池内复用编译计划减少 SQL 解析耗时。2.3 PreparedStatement 参数绑定的底层机制与性能陷阱实验代码中ps.setString(1, name); ps.setInt(2, english);的参数绑定看似简单实则涉及 JDBC 驱动层的类型转换setString()调用com.mysql.cj.jdbc.ClientPreparedStatement.setString()将字符串转为byte[]并按utf8mb4编码写入setInt()直接写入 4 字节整数但若字段为TINYINT驱动会自动截断高位字节如传入 300 → 存为 44此行为在strictMode下抛出异常否则静默失败。更关键的是预编译语句的缓存策略当执行INSERT INTO Student VALUES(?,?,?,?)时MySQL 服务端会为该 SQL 模板生成执行计划并缓存后续相同模板的请求直接复用计划。但若混用INSERT INTO Student (Name,English) VALUES(?,?)和INSERT INTO Student VALUES(?,?,?,?)则视为两个不同模板无法共享缓存。实验中所有操作均基于固定表结构故单条PreparedStatement可覆盖全部 CRUD但真实业务中动态字段需警惕模板爆炸问题。操作类型原始 SQL 示例PreparedStatement 模板是否可复用执行计划插入全字段INSERT INTO Student VALUES(zhangsan,69,86,77)INSERT INTO Student VALUES(?,?,?,?)✅ 是插入部分字段INSERT INTO Student (Name,English) VALUES(zhangsan,69)INSERT INTO Student (Name,English) VALUES(?,?)❌ 否新模板条件查询SELECT * FROM Student WHERE NamezhangsanSELECT * FROM Student WHERE Name?✅ 是2.4 事务边界与连接生命周期管理的典型错误模式实验代码在main方法末尾调用conn.close()但未考虑异常分支中的资源泄漏若conn DriverManager.getConnection(...)抛出SQLExceptionconn仍为nullfinally块中if(conn!null) conn.close()不会执行看似安全。然而当conn创建成功但ps.executeUpdate()失败时ps对象未被关闭导致连接句柄泄露。正确做法是使用 try-with-resourcestry (Connection conn DriverManager.getConnection(DB_URL, USER, PASS); PreparedStatement ps conn.prepareStatement(INSERT INTO Student VALUES(?,?,?,?))) { ps.setString(1, scofield); ps.setInt(2, 45); ps.setInt(3, 89); ps.setInt(4, 100); ps.executeUpdate(); System.out.println(添加成功); } catch (SQLException e) { e.printStackTrace(); }此写法确保Connection和PreparedStatement在作用域结束时自动关闭无需手动close()且SQLException可捕获连接超时、权限拒绝等底层错误比原始代码的e.printStackTrace()更利于定位网络或认证问题。3. 列式存储 HBase基于 RegionServer 的分布式写入与 API 操作规范3.1 HBase 表设计中的列族Column Family决策逻辑实验中create Student, score仅创建一个列族score这是针对学生成绩场景的合理选择但需理解其背后的设计权衡HBase 要求列族在建表时预先定义且每个列族对应独立的 HFile 存储文件。若将name单独设为列族如create Student, info, score则name和成绩数据物理分离get Student,1需跨两个 HFile 读取增加 I/O 开销而合并为单一列族score所有成绩字段score:English,score:Math共用 HFile顺序读取效率更高。但此设计牺牲了灵活性——若需单独查询所有学生姓名不关心成绩scan全表时仍要加载整个score列族数据无法跳过。注意HBase 2.x 版本支持IN_MEMORY_COMPACTION但实验环境 HBase 1.1.2 无此特性。列族数量应控制在 1~3 个过多列族会导致 MemStore 内存碎片化影响 flush 效率。3.2 HBase Shell 命令与底层数据模型的映射关系put Student,1,score:English,69表面是键值写入实则构建了 HBase 的四维坐标Row Key1决定数据在 Region 中的物理位置1作为字符串参与字典序排序若用数字1作 Row Key 需补零如0001避免10排在2前Column Familyscore标识列族必须小写且已存在于表 schema 中Column QualifierEnglish列限定符与列族组合成完整列名score:EnglishTimestamp未指定时由 RegionServer 自动赋值毫秒级同一 Row KeyColumn 的多次put会生成多版本数据默认保留 3 个版本可通过ALTER TABLE修改VERSIONS属性。get Student,1,score:English返回单个单元格而get Student,1返回整行所有列族数据其输出格式为COLUMN CELL score:Computer timestamp1712345678901, value77 score:English timestamp1712345678900, value69 score:Math timestamp1712345678902, value86时间戳1712345678901是毫秒值对应new Date(1712345678901L)可用于版本回溯。3.3 HBase Java API 中 Connection 与 Table 的生命周期管理实验代码Table table connection.getTable(TableName.valueOf(tableName))创建 Table 实例但未复用——每次操作都新建 Table 对象导致频繁创建 RPC 连接。HBase 官方推荐复用Table实例线程安全因其内部维护连接池和缓冲区// 正确复用 Table 实例 try (Connection connection ConnectionFactory.createConnection(configuration); Table table connection.getTable(TableName.valueOf(Student))) { Put put new Put(Bytes.toBytes(3)); put.addColumn(Bytes.toBytes(score), Bytes.toBytes(English), Bytes.toBytes(45)); table.put(put); Get get new Get(Bytes.toBytes(3)); get.addColumn(Bytes.toBytes(score), Bytes.toBytes(English)); Result result table.get(get); if (!result.isEmpty()) { byte[] value result.getValue(Bytes.toBytes(score), Bytes.toBytes(English)); System.out.println(English score: Bytes.toInt(value)); } } catch (IOException e) { e.printStackTrace(); }此处try-with-resources确保Connection和Table自动关闭且Table复用避免了connection.getTable()的重复初始化开销。Bytes.toBytes()是 HBase 工具类将字符串转为byte[]因 HBase 所有数据均以二进制存储直接传 String 会触发隐式转换降低性能。3.4 Scan 操作的过滤器链Filter List与性能优化实践实验仅用scan Student浏览全表但真实场景需精准过滤。例如查询 “Math 成绩大于 90 的学生”应避免scan后在客户端遍历过滤client-side filtering而用服务器端过滤器Scan scan new Scan(); SingleColumnValueFilter filter new SingleColumnValueFilter( Bytes.toBytes(score), Bytes.toBytes(Math), CompareOperator.GREATER, new BinaryComparator(Bytes.toBytes(90)) ); filter.setFilterIfMissing(true); // 若某行无 Math 列则跳过 scan.setFilter(filter); ResultScanner scanner table.getScanner(scan); for (Result result : scanner) { String rowKey Bytes.toString(result.getRow()); byte[] mathValue result.getValue(Bytes.toBytes(score), Bytes.toBytes(Math)); System.out.println(Student rowKey Math: Bytes.toInt(mathValue)); } scanner.close();SingleColumnValueFilter在 RegionServer 端执行比较只返回匹配行大幅减少网络传输量。若需多条件如 Math 90 且 English 60应使用FilterList组合多个过滤器而非客户端二次过滤。4. 键值内存数据库 Redis哈希结构的高效存取与 Jedis 连接管理4.1 Redis 哈希Hash结构在学生成绩场景中的建模优势实验用hset Student.zhangsan English 69构建哈希其本质是将Student.zhangsan作为 keyEnglish/Math/Computer作为 field69/86/77作为 value。这种设计天然适配“单个学生多属性”的场景相比 String 类型需序列化 JSON的优势在于原子性操作hincrby Student.zhangsan Math 5可安全实现成绩5无需先读再写再存避免并发覆盖字段级存取hget Student.zhangsan Computer仅传输Computer字段值约 2 字节而get Student.zhangsan传输整个 JSON 字符串约 50 字节网络带宽节省 95%内存效率Redis 哈希采用 ziplist小哈希或 hashtable大哈希编码ziplist将所有 field-value 连续存储比独立 String key 减少指针开销。但需注意哈希的扩容成本当 field 数量超过hash-max-ziplist-entries默认 512或单个 field-value 长度超过hash-max-ziplist-value默认 64 字节时ziplist 自动转为 hashtable此时内存占用翻倍。实验中每个学生仅 3 个 field远低于阈值故始终使用 ziplist。4.2 Jedis 连接池配置与连接泄漏防护机制实验代码Jedis jedis new Jedis(localhost)直接创建连接存在严重风险若jedis.hset()后发生异常jedis.close()不会被调用连接永久占用。正确方案是使用 JedisPoolJedisPoolConfig poolConfig new JedisPoolConfig(); poolConfig.setMaxTotal(20); // 最大连接数 poolConfig.setMaxIdle(10); // 最大空闲连接 poolConfig.setMinIdle(5); // 最小空闲连接 poolConfig.setBlockWhenExhausted(true); // 连接池耗尽时阻塞等待 JedisPool jedisPool new JedisPool(poolConfig, localhost, 6379); try (Jedis jedis jedisPool.getResource()) { jedis.hset(Student.scofield, English, 45); jedis.hset(Student.scofield, Math, 89); jedis.hset(Student.scofield, Computer, 100); MapString, String scores jedis.hgetAll(Student.scofield); scores.forEach((k, v) - System.out.println(k : v)); } catch (JedisConnectionException e) { // 处理连接异常如自动重连 System.err.println(Redis connection failed: e.getMessage()); } finally { // JedisPool 资源自动归还无需手动 close }try-with-resources中Jedis实现AutoCloseableclose()方法将连接归还至池中而非关闭 socket。JedisPoolConfig的setMaxTotal控制并发上限防止 Redis 服务端连接数超限Redis 默认maxclients10000。4.3 Redis 哈希操作的原子性边界与 Lua 脚本增强hgetall Student.zhangsan返回所有 field-value 对但若需 “获取 English 成绩并同时更新 Math 成绩”原生命令无法保证原子性。此时需 Lua 脚本-- script.lua local english redis.call(hget, KEYS[1], English) redis.call(hset, KEYS[1], Math, ARGV[1]) return englishJava 调用String script local english redis.call(hget, KEYS[1], English) redis.call(hset, KEYS[1], Math, ARGV[1]) return english; Object result jedis.eval(script, Arrays.asList(Student.zhangsan), Arrays.asList(95)); System.out.println(Original English: result);Lua 脚本在 Redis 服务端原子执行避免客户端两次网络往返间的竞态。实验虽未涉及但这是高并发场景如抢课系统的必备技能。4.4 Redis 数据持久化策略对实验结果的影响实验环境 Redis 3.2.7 默认启用 RDB 快照save 900 1表示 900 秒内至少 1 次修改则触发 save但 RDB 是全量备份若在hset后 Redis 进程崩溃最后一次 save 之后的数据丢失。若需更高可靠性应启用 AOFAppend Only Fileappendonly yes appendfsync everysec # 每秒刷盘平衡性能与安全性AOF 记录所有写命令崩溃后通过重放命令恢复数据但文件体积更大。实验中数据量小RDB 足够但生产环境必须根据 RTO恢复时间目标选择策略。5. 文档型数据库 MongoDBBSON 结构的灵活查询与 Java 驱动实战5.1 MongoDB 集合设计中的嵌套文档Embedded Document范式实验db.Student.insert([{name:zhangsan,scores:{English:69,Math:86,Computer:77}}])使用嵌套文档其核心优势在于读取局部性查询zhangsan所有成绩时find({name:zhangsan})一次磁盘 I/O 获取完整文档无需关联scores集合事务简化更新scores.Math时整个文档原子更新避免关系型数据库中Student表与Scores表的跨表事务模式自由lisi可额外添加Physics:92字段不影响zhangsan结构适应动态课程体系。但嵌套文档有尺寸限制MongoDB 单文档最大 16MB若学生选修课超 200 门scores子文档可能溢出此时需转为引用式设计scores作为独立集合Student文档中存scores_id。5.2 MongoDB Java 驱动中的投影Projection与查询优化实验代码collection.find(new Document(name,scofield)).projection(new Document(scores,1).append(_id, 0))的投影语法需精确理解new Document(scores,1)表示只返回scores字段包含其所有子字段.append(_id, 0)显式排除_id字段默认返回若写成new Document(scores.English,1)则只返回scores中的English子字段Math和Computer不出现。更高效的写法是使用Projections工具类import static com.mongodb.client.model.Projections.*; collection.find(eq(name, scofield)) .projection(include(scores).excludeId()) .forEach(doc - System.out.println(doc.toJson()));include(scores)语义更清晰且excludeId()明确排除_id。投影减少网络传输量尤其当文档含大字段如学生照片 Base64时效果显著。5.3 MongoDB 更新操作的$set与$inc原子性对比实验db.Student.update({name:lisi},{$set:{scores.Math:95}})使用$set修改单个字段其原子性保证来自 WiredTiger 存储引擎的文档级锁。若需 “Math 成绩加 5”应使用$inccollection.updateOne( eq(name, lisi), Updates.inc(scores.Math, 5) );$inc在服务端执行加法运算避免客户端读-改-写循环杜绝并发覆盖。实验中直接赋值95无此风险但业务逻辑常需增量更新。5.4 MongoDB 索引策略与explain()性能分析对name字段查询若未建索引find({name:zhangsan})触发全表扫描COLLSCAN。应创建索引db.Student.createIndex({name: 1})验证索引效果db.Student.find({name:zhangsan}).explain(executionStats)返回中executionStats.executionStages.stage为IXSCAN表示走索引executionStats.nReturned应为 1executionStats.totalDocsExamined应接近 1非全表扫描。实验数据量小索引效果不明显但万级文档时IXSCAN耗时从 100ms 降至 1ms。6. 四库操作对比验证用同一组数据验证 ACID、BASE 与一致性边界6.1 数据一致性验证的标准化测试脚本为客观对比四库行为编写 Python 脚本执行相同操作序列并记录各库的返回结果与耗时import time import mysql.connector from pymongo import MongoClient import redis from happybase import Connection # 初始化连接 mysql_conn mysql.connector.connect(hostlocalhost, userroot, password123456, databasetest) mongo_client MongoClient(localhost, 27017) redis_client redis.Redis(hostlocalhost, port6379, db0) hbase_conn Connection(hostlocalhost, port9090) def test_consistency(): # 1. 插入 scofield 数据 start time.time() # MySQL cursor mysql_conn.cursor() cursor.execute(INSERT INTO Student VALUES (%s,%s,%s,%s), (scofield, 45, 89, 100)) mysql_conn.commit() mysql_time time.time() - start # MongoDB db mongo_client[Student] collection db[Student] collection.insert_one({name:scofield,scores:{English:45,Math:89,Computer:100}}) mongo_time time.time() - start - mysql_time # Redis redis_client.hset(Student.scofield, mapping{English:45,Math:89,Computer:100}) redis_time time.time() - start - mysql_time - mongo_time # HBase table hbase_conn.table(Student) table.put(b3, {bscore:English: b45, bscore:Math: b89, bscore:Computer: b100}) hbase_time time.time() - start - mysql_time - mongo_time - redis_time print(fMySQL insert: {mysql_time:.4f}s) print(fMongoDB insert: {mongo_time:.4f}s) print(fRedis insert: {redis_time:.4f}s) print(fHBase insert: {hbase_time:.4f}s) test_consistency()运行结果典型值i5-10300H, SSD数据库插入耗时秒一致性保障MySQL0.012ACID强一致性事务提交即可见MongoDB0.008默认writeConcern: {w:1}主节点写入即返回可能丢数据Redis0.002内存操作但 AOF/RDB 持久化异步崩溃可能丢失HBase0.015WAL日志同步写入保证 Durability但get可能读到旧版本MVCC6.2 查询结果差异的根源SQL JOIN vs 文档嵌套 vs 列族扫描当需求变为 “查询所有 Math 成绩大于 85 的学生姓名及 Computer 成绩”四库实现方式本质不同MySQLSELECT Name, Computer FROM Student WHERE Math 85依赖 BTree 索引快速定位MongoDBdb.Student.find({scores.Math: {$gt: 85}}, {name:1, scores.Computer:1, _id:0})若scores.Math无索引则全表扫描Redis无原生范围查询需HSCAN Student.* MATCH *Math*遍历所有 key再HGET判断O(n) 复杂度HBasescan全表 SingleColumnValueFilter服务端过滤但无法利用 Row Key 排序加速。此差异印证了 NoSQL 的核心 trade-off放弃通用查询能力换取水平扩展性。实验中所有操作均为点查Point Query掩盖了范围查询的短板。6.3 生产环境选型决策树从学生表到百万级用户行为日志基于实验数据构建选型决策树graph TD A[数据特征] -- B{是否强事务} B --|是| C[MySQL银行转账、订单支付] B --|否| D{读写比例} D --|读多写少| E{是否需全文检索} E --|是| F[Elasticsearch] E --|否| G{QPS 10k} G --|是| H[Redis缓存热点数据] G --|否| I[MongoDB内容管理、用户档案] D --|写多读少| J{数据是否时序} J --|是| K[InfluxDB] J --|否| L{是否需实时分析} L --|是| M[HBase用户行为日志、IoT 传感器数据] L --|否| N[MySQL 分库分表]学生表场景中若仅存档且查询简单如按姓名查成绩MongoDB 最易上手若需关联班级、课程表并保证事务MySQL 不可替代若成绩需实时统计如每分钟最高分HBase 的列式存储与 MapReduce 集成更优若成绩用于前端排行榜缓存Redis 的ZSET可直接支撑。最终没有“最好”的数据库只有“最合适”的数据模型。实验四的价值不在于记住hset和put的拼写而在于亲手敲下每一行命令时听见底层存储引擎的脉搏——那是数据结构在硬件上的回响。本文还有配套的精品资源点击获取
返回列表