注: 文中讲述的原理是推理和探讨 , 和现实中的实现不必定彻底相同 。html
数据库呢 , 主要分为 5 大部分 , 数据库
1 Sql 分析器服务器
2 查询(更新)计划器并发
3 数据存储检索高并发
4 优化策略性能
5 事务(Transaction)优化
第一个部分 Sql 分析器 呢 , 涉及到 编译原理 语法分析 的 知识 和 关系运算 的 知识 , 但这并不难 , 我写了一个项目 SelectDataTable , 能够解析简单的 Sql 语句 , 经过 Sql 语句在 DataTable 中查询数据 , 能够参考 : http://www.javashuo.com/article/p-qbebfnqj-hd.html设计
第二个部分 查询(更新)计划器 , 这个部分就是把 Sql 解析的结果 转换为 数据存储检索的 指令 。日志
第三个部分 数据存储检索 , 就是 数据如何在磁盘上存储和检索 。 咱们来详细谈一下这个部分 。htm
数据 在 磁盘上 存储检索 的 基础 , 是 数据块(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 能够是 共享锁 。
基本上就这些 。
按照这个原理 , 能够写一个 数据库 。 呵呵呵呵