疾风式全栈(12)-存储之数据库(草稿)
这一次我们来关注企业软件的核心,数据库.
之前我们看了计算机中的存储主要分两类,以内存为代表的易失性存储,和以硬盘为代表的持久化存储.我们需要长期保存数据,就要把数据写入到硬盘上进行存储.对硬盘的管理一般是要进行分区,在分区中建立文件系统.而数据库也是在文件系统之上建立一套对于大量数据的管理规范.简单的说,就是一堆和数据相关的文件.
从根本上来说,我们自己建立一些文件来存储数据也是可以的.但是当有大量的数据需要管理时,一般都会采用通用的数据库技术.这几十年来数据库的主流是关系数据库.包括oracle,sql server, mysql,db2, postgre sql等产品.非关系型的数据库近年来也大量涌现,不仅是存储格式改变,也带来了内存数据库等新理念.
我们还是先从关系数据库开始.我们不用严格的数学概念,就照最简单的理解.关系就是行和列组成的表格.跟excel表格差不多.为了规范和高密度存储,对表格的形式作了一些约束,称为范式.总的原则就是相关的数据放在一起,有些数据有一些相关性,就放在不同的表里,通过编号彼此联系起来.当然这些都会有一些术语,行的唯一编号叫主键,相关数据的编号叫外键.
对数据库的操作也有一个标准规范叫sql.上面说的这些关系数据库都支持sql,好像也只支持sql.不管是用图形界面还是软件接口去连接数据库,发送的都是sql语句.甚至非关系数据库和大数据框架都在努力支持或模仿sql.
各个数据库所支持的sql会有一些区别,但是基本是一致的.和前端技术一样,也是有标准规范和各个浏览器的不同实现.常用的操作,使用标准的语法基本都能满足.
那么sql的具体形式是什么样呢?其实sql确实是非常先进的技术,跟我们前面说的函数式编程很像.只需要声明操作,不需要关心具体的执行过程. 但是sql多数采用的是空格分隔的单词作为语句,类似英语,没有全部使用点和小括号来调用.但是思路是一致的.sql里的where类似高阶函数filter,而select类似map.
比如,一张表叫c,有两列分别叫a和b,sql的查询可能是
select b from c where a = 1
在js里对应的说法就是,有一个数组c,里面存的对象都有a和b两个属性,查询
c.filter(it => it.a == 1).map(it => it.b)
是不是很相似,一样的思路,都是先查找,再变换.只是写法上有些区别,特别是sql的查询都是把select写在最前面,其实执行的时候,也是先执行where再select的.
从这里我们也可以看到,不管是硬盘上的数据库,还是内存中的集合,本质都是存储数据,处理的思路也是一样的.所以现在大家也在探讨一种统一的方式进行处理.既可以是把数据库放在内存中,比如alasql这样的库和各种内存数据库,也可以用操作集合的方式操作数据库,比如linq和datomic.而像spark这样的分布式大数据框架,致力于对各种来源各种形式的数据提供统一的操作体验.在spark的api里,filter和where是同义词,可以自由选择.
而reduce的操作,在数据库中直接提供了最常用的实现.比如聚合函数,sum avg min max count,可以理解为相应reduce的简写.
比如
select sum(a) from c
相当于
c.reduce((it,next) => it.a + next.a)
箭头函数提供了sum(a)最基本的逻辑,而reduce自动进行了整个集合的循环,得到了总和.
另外一个常用的操作group by分组, 也可以理解成是reduce(事实上,map和filter都可以理解成reduce). 具体用法大家可以自己搜索.
join
我认为关系数据库中最麻烦的地方就在于连接.上面说了,对于有一些关联,又不是特别密切的数据,会拆开放在两个表里,但在需要使用的场合也要把他们联合起来.联合的依据是外键.一般都是外键与主键相等的作为一条数据.这叫内连接.
select a.b,c.d from a inner join c on a.fk = c.id
也有外连接,把那些没有匹配到的数据也展示出来.大家可以需要时自己搜索.
增删改
添加数据
insert into c (a,b) values (1,2)
修改数据
update c set a = 1 where b = 2
删除
delete from c where b = 2
如果没有where条件,会自动循环操作所有行.所以一定要考虑清楚,多数时候是要加上where的.
而我们一直提倡函数式的风格,建议不要进行数据的修改和删除,可以用时间戳控制和增加新的数据代替.
在sql编写时,一定要理解,关系运算的结果还是关系.不管多复杂的运算,一层一层的分解,弄清每一层产生怎样的关系,有哪些行哪些列,总是可以得到正确结果的.至于对于性能的理解和优化,就需要真正的经验积累了.
虽然并不建议编写复杂的sql,但是鉴于目前数据库领域的现状,很多时候用sql还是最直接有效的解决方式.sql毕竟是声明式的,理清思路之后,几行语句可能相当于写几十行代码.sql的问题在我看来是缺少一些关键的特性.毕竟出现于几十年前,而且基本上没有什么大的改变和进步.
- 缺少局部引用.嵌套多层的查询要写多层的括号,难以阅读.视图提供了一种对查询的引用,但是视图是持久化的.某些数据库提供了私有的扩展方案,但是更多的场合还是没有合适的通用解决方案.
- 连接不能自动实现.尽管外键已经声明,连接表时还是要手动一一指定连接条件.这部分工作完全可以隐式实现,减少大量的模版代码.JPQL等技术已经做了这样的工作,但是没有进入sql标准.(某些数据库有natural join,不过没有成为标准,而且方便性也不如JPQL的风格)
- 缺少模块化机制,所有表基本都是放在全局命名空间中,管理不便.oracle也尝试提供用户,表空间- 等机制来隔离管理,但使用也不是很方便.
- 缺少内置的时间和状态管理.只存储数据的最新值,并且在原地破坏式的更新,让历史数据管理变得困难重重,十分危险.很多业务表需要自己维护create time, update time.而在datomic里时间是内置的数据元素.
所以,尽管有一些场景会用到数据库提供的存储过程,提供了对sql的一些自己的扩展.但是并没有标准化,无法在不同数据库之间通用.
高效的存储是一门重要的学问.涉及到硬件和软件的方方面面,随着计算机和网络技术的深入应用,产生了大量的电子数据需要存储,也需要高效的进行分析处理.因此产生了SSD,SAS,SATA,NAS,SAN等很多新技术,大家可以搜索了解.