zoukankan      html  css  js  c++  java
  • 谈谈 数据库原理

    注:  文中讲述的原理是推理和探讨 , 和现实中的实现不一定完全相同 。

    数据库呢 , 主要分为 5 大部分 , 

    1 Sql 分析器

    2 查询(更新)计划器

    3 数据存储检索

    4 优化策略

    5 事务(Transaction)

    第一个部分 Sql 分析器 呢 , 涉及到 编译原理 语法分析 的 知识 和 关系运算 的 知识 , 但这并不难 , 我写了一个项目 SelectDataTable , 可以解析简单的 Sql 语句 , 通过 Sql 语句在 DataTable 中查询数据 , 可以参考 :  https://www.cnblogs.com/KSongKing/p/9455216.html

    第二个部分 查询(更新)计划器 , 这个部分就是把 Sql 解析的结果 转换为 数据存储检索的 指令 。

    第三个部分 数据存储检索 , 就是 数据如何在磁盘上存储和检索 。 我们来详细谈一下这个部分 。

    数据 在 磁盘上 存储检索 的 基础 , 是 数据块(Data Block) , 就是说 , 把要存储的数据分成一个一个的 数据块 。 比如 , 我们可以定义数据块的大小是 4K 。

    那么 , 在数据库里,数据是以 表 和 表记录 的 形式存在的 , 那么就把表记录放到 数据块 里 存储 。 当然 一笔表记录 的 大小 不能超过 数据块的大小 。

    那么如何 检索 呢 ? 将 数据块 从 磁盘读取到 内存 , 在内存里进行检索 。

    如何 更新 呢 ? 如果 数据 所在的 数据块 已经在 内存 里 , 就先对 内存里的数据块更新, 在 适当的时候 再批量更新到 磁盘 上 。 如果 数据 不在内存里 , 需要直接更新磁盘 。 从这里可以看出来 , 更新 可能 频繁 写磁盘 , 需要 频繁移动 磁头 , 在 固态硬盘 的 时代 , 这个问题可能会改善很多 。 另外也可以看出来 , 如果 内存 足够大 , 那么可以把 大量的数据 加载到 内存 里 在内存里 查询 更新 , 在适当的时候才 批量 写入 磁盘 , 这样处理速度可以加快 。 换句话说 , 内存 的 充分 对于 数据库 效率 很重要 。 实际的经验中 , 看到的情况大致也是这样 。 ^ ^    有充分的内存 , 数据库 可以把 整张表的资料 和 索引 都 加载到 内存 , 这样 查询 和 更新 的 速度 是很快的 。 而 经验中 也经常会有这样的经验 : 第一次查询的时候会比较慢 , 后面就快了 。 实际上 就跟 数据库 加载 数据 到 内存 的 这个 原理有关 。

    但上面说的有一点也不对 。 如果 数据 已经在 内存 里 , 那么更新了 内存 里的数据后 , 应立即更新 磁盘 上的数据 。 不然如果 服务器 突然断电 , 数据就丢失了 。 对于 客户端 来说 , 执行 insert update delete 成功后 , 就意味着 数据 已经 持久化 。

    数据库 通常 会把 数据 存放在一个 文件 里 。 比如 Sql Server 。 通过 FileStream 的 Position 属性 , 我们可以 指定位置 写入 和 读取 数据块 , 以及 指定位置 直接更新 数据块 里的 数据 。 这样 , 文件就可以看作一块 地址空间 , 就像 内存 一样 , 可以像 管理 内存 一样 管理 。 当然 , 这是从 地址 这个角度来看是这样 。 从 硬件属性 来看 , 还是要考虑 磁盘 的 机械读写 的 特性 , 顺序读写 的 效率 比 随机读写 好 , 所以 据说 B Tree 索引 就是 顺序存储 索引 的 , 而 B Tree 是使用最广泛的 索引 了 吧 !

    但 总的来说 , 固态硬盘 的 出现 , 会使这些问题 改善 很多 。

    第四个部分 , 优化策略 主要是 临时索引 和 并行计算 等 。 临时索引 是 很有用的 , 它可以使 数据库 变得 “傻瓜化” , 不需要刻意的去设计和建立索引 , 就可以获得高效的查询性能 。 另外 , 完全依靠人工设计和建立索引也是很大的工作量 , 同时 , 固定的索引会在每次更新表时都要更新索引 , 同时索引会一直占用存储空间 , 所以 临时索引 还让 数据库 的 使用 轻松 灵活 了 。

    另外就是 并行计算 , 并行计算 看起来 很诱人,很美好 , 但是仔细想想好像不是那么回事 。 数据库 通常处于 并发的场景下 。 在 高并发 下, 每个 CPU 核 都会处理 n 个 请求 , 如果还要把每个请求的查询任务分成若干个任务并行执行 , 好像意义不大 。 

    第五个部分 , 事务 是 数据库 的 重头戏 。 事务 通过 事务日志(Transaction Log) 实现 。 当一个事务开始时 , 首先会在事务日志中记录该事务已开始 , 并且只有在事务日志中记录日志成功 , 才会开始下一步的操作 。 对于事务来讲 , 为了保证 数据完整性 , 或者说 ACID , 需要这样严谨的进行 。 可以说是 “环环相扣” 。 接下来就开始执行更新操作 , 每一个更新操作 , 会 分为 3 个 步骤 : 1 在事务日志中记录 Begin(包括 要执行什么样的 操作 的 信息) , 2 执行更新操作 , 3 在事务日志中记录 End 。 事务完成后 , 会再记录整个事务 End 。 只有到这一步 , 整个事务才算结束 , 更新才彻底生效 。 正常情况下 , 如果需要回滚 , 可以根据 事务日志 来 回滚 , 这容易理解 , 就不详细描述了 。 在异常情况下 , 比如 服务器 突然断电 , 在这样的情况下 , 要如何处理 , 才能使 数据 正确呢 ? 数据库 在 重新启动 时 , 会检查 事务日志 , 会发现 未完成的 事务日志(没有记录 End 的) , 数据库 会 对 未完成 的 事务 进行 回滚 。

    事务 另外一个方面就是 锁(Lock) 。 在 事务 开始时 , 会锁定表 , 这意味着 从现在起 , 不允许对表开始新的操作 , 同时 要求 在当前所有对表的操作(包括 select) 结束后 , 才会开始本次事务的 操作 。 那要怎么才能确定当前对表的操作 都结束了呢 ? 这大概还是需要通过 锁 。 普通的 insert update delete select 也需要获得锁 ,  这个 锁 应该是 行级锁 。 insert update delete 应该是 独占锁 , select 可以是 共享锁 。  

    基本上就这些 。

    按照这个原理 , 可以写一个 数据库 。    呵呵呵呵

  • 相关阅读:
    android 多线程
    Uva 10881 Piotr’s Ants 蚂蚁
    LA 3708 Graveyard 墓地雕塑 NEERC 2006
    UVa 11300 Spreading the Wealth 分金币
    UVa 11729 Commando War 突击战
    UVa 11292 The Dragon of Loowater 勇者斗恶龙
    HDU 4162 Shape Number
    HDU 1869 六度分离
    HDU 1041 Computer Transformation
    利用可变参数函数清空多个数组
  • 原文地址:https://www.cnblogs.com/KSongKing/p/9492315.html
Copyright © 2011-2022 走看看