做利用开发的同学经常感觉数据库由 DBA 运维,本人会写 SQL 就能够了,数据库原理不须要学习。其实即便是写 SQL 也须要理解数据库原理,比方咱们都晓得,SQL 的查问条件尽量蕴含索引字段,然而为什么呢?这样做有什么益处呢?你兴许会说,应用索引进行查问速度快,然而为什么速度快呢?
此外,咱们在 Java 程序中拜访数据库的时候,有两种提交 SQL 语句的形式,一种是通过 Statement 间接提交 SQL;另一种是先通过 PrepareStatement 预编译 SQL,而后设置可变参数再提交执行。
Statement 间接提交的形式如下:
statement.executeUpdate("UPDATE Users SET stateus = 2 WHERE userID=233");
PrepareStatement 预编译的形式如下:
PreparedStatement updateUser = con.prepareStatement("UPDATE Users SET stateus = ? WHERE userID = ?");
updateUser.setInt(1, 2);
updateUser.setInt(2,233);
updateUser.executeUpdate();
看代码,仿佛第一种形式更加简略,然而编程实际中,次要用第二种。应用 MyBatis 等 ORM 框架时,这些框架外部也是用第二种形式提交 SQL。那为什么要舍简略而求简单呢?
要答复下面这些问题,都须要理解数据库的原理,包含数据库的架构原理与数据库文件的存储原理。
数据库架构与 SQL 执行过程
咱们先看看数据库架构原理与 SQL 执行过程。
关系数据库系统 RDBMS 有很多种,然而这些关系数据库的架构基本上差不多,包含反对 SQL 语法的 Hadoop 大数据仓库,也基本上都是类似的架构。一个 SQL 提交到数据库,通过连接器将 SQL 语句交给语法分析器,生成一个形象语法树 AST;AST 通过语义剖析与优化器,进行语义优化,使计算过程和须要获取的两头数据尽可能少,而后失去数据库执行打算;执行打算提交给具体的执行引擎进行计算,将后果通过连接器再返回给应用程序。
应用程序提交 SQL 到数据库执行,首先须要建设与数据库的连贯,数据库 连接器 会为每个连贯申请调配一块专用的内存空间用于会话上下文治理。建设连贯对数据库而言绝对比拟重,须要破费肯定的工夫,因而应用程序启动的时候,通常会初始化建设一些数据库连贯放在连接池里,这样当解决内部申请执行 SQL 操作的时候,就不须要破费工夫建设连贯了。
这些连贯一旦建设,不论是否有 SQL 执行,都会耗费肯定的数据库内存资源,所以对于一个大规模互联网利用集群来说,如果启动了很多应用程序实例,这些程序每个都会和数据库建设若干个连贯,即便不提交 SQL 到数据库执行,也就会对数据库产生很大的压力。
所以应用程序须要对数据库连贯进行治理,一方面通过连接池对连贯进行治理,闲暇连贯会被及时开释;另一方面微服务架构能够大大减少数据库连贯,比方对于用户数据库来说,所有利用都须要连贯到用户数据库,而如果划分一个用户微服务并独立部署一个比拟小的集群,那么就只有这几个用户微服务实例须要连贯用户数据库,须要建设的连贯数量大大减少。
连接器收到 SQL 当前,会将 SQL 交给 语法分析器 进行解决,语法分析器工作比拟简单机械,就是依据 SQL 语法规定生成对应的形象语法树。
如果 SQL 语句中存在语法错误,那么在生成语法树的时候就会报错,比方,上面这个例子中 SQL 语句里的 where 拼写错误,MySQL 就会报错。
mysql> explain select * from users whee id = 1;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'id = 1' at line 1
因为语法错误是在构建形象语法树的时候发现的,所以可能晓得,谬误是产生在哪里。下面例子中,尽管语法分析器不能晓得 whee 是一个语法拼写错误,因为这个 whee 可能是表名 users 的别名,然而语法分析器在构建语法树到了 id=1
这里的时候就出错了,所以返回的报错信息能够提醒,在 'id = 1'
左近有语法错误。
语法分析器生成的形象语法树并不仅仅能够用来做语法校验,它也是下一步解决的根底。语义剖析与优化器会对形象语法树进一步做语义优化,也就是在保障 SQL 语义不变的前提下,进行语义等价转换,使最初的计算量和两头过程数据量尽可能小。
比方对于这样一个 SQL 语句,其语义是示意从 users 表中取出每一个 id 和 order 表以后记录比拟,是否相等。
select f.id from orders f where f.user_id = (select id from users);
事实上,这个 SQL 语句在语义上等价于上面这条 SQL 语句,表间计算关系更加清晰。
select f.id from orders f join users u on f.user_id = u.id;
SQL 语义剖析与优化器就是要将各种简单嵌套的 SQL 进行语义等价转化,失去无限几种关系代数计算构造,并利用索引等信息进一步进行优化。能够说,各个数据库最黑科技的局部就是在优化这里了。
语义剖析与优化器最初会输入一个执行打算,由执行引擎实现数据查问或者更新。MySQL 执行打算的例子如下:
执行引擎是可替换的,只有可能执行这个执行打算就能够了。所以 MySQL 有多种执行引擎(也叫存储引擎)能够抉择,缺省的是 InnoDB,此外还有 MyISAM、Memory 等,咱们能够在创立表的时候指定存储引擎。大数据仓库 Hive 也是这样的架构,Hive 输入的执行打算能够在 Hadoop 上执行。
应用 PrepareStatement 执行 SQL 的益处
好了,理解了数据库架构与 SQL 执行过程之后,让咱们回到结尾的问题,应用程序为什么应该应用 PrepareStatement 执行 SQL?
这样做次要有两个益处。
一个是 PrepareStatement 会事后提交带占位符的 SQL 到数据库进行预处理,提前生成执行打算,当给定占位符参数,真正执行 SQL 的时候,执行引擎能够间接执行,效率更好一点。
另一个益处则更为重要,PrepareStatement 能够避免 SQL 注入攻打。假如咱们容许用户通过 App 输出一个名字到数据中心查找用户信息,如果用户输出的字符串是 Frank,那么生成的 SQL 是这样的:
select * from users where username = 'Frank';
然而如果用户输出的是这样一个字符串:
Frank';drop table users;--
那么生成的 SQL 就是这样的:
select * from users where username = 'Frank';drop table users;--';
这条 SQL 提交到数据库当前,会被当做两条 SQL 执行,一条是失常的 select 查问 SQL,一条是删除 users 表的 SQL。黑客提交一个申请而后 users 表被删除了,零碎解体了,这就是 SQL 注入攻打。
如果用 Statement 提交 SQL 就会呈现这种状况。
但如果用 PrepareStatement 则能够防止 SQL 被注入攻打。因为一开始结构 PrepareStatement 的时候就曾经提交了查问 SQL,并被数据库事后生成好了执行打算,前面黑客不论提交什么样的字符串,都只能交给这个执行打算去执行,不可能再生成一个新的 SQL 了,也就不会被攻打了。
select * from users where username = ?;
数据库文件存储原理
回到文章结尾提出的另一个问题,数据库通过索引进行查问能放慢查问速度,那么,为什么索引能放慢查问速度呢?
数据库索引应用 B + 树,咱们先看下 B + 树这种数据结构。B+ 树是一种 N 叉排序树,树的每个节点蕴含 N 个数据,这些数据按程序排好,两个数据之间是一个指向子节点的指针,而子节点的数据则在这两个数据大小之间。
如下图。
B+ 树的节点存储在磁盘上,每个节点存储 1000 多个数据,这样树的深度最多只有 4 层,就可存储数亿的数据。如果将树的根节点缓存在内存中,则最多只须要三次磁盘拜访就能够检索到须要的索引数据。
B+ 树只是放慢了索引的检索速度,如何通过索引放慢数据库记录的查问速度呢?
数据库索引有两种,一种是聚簇索引,聚簇索引的数据库记录和索引存储在一起,下面这张图就是聚簇索引的示意图,在叶子节点,索引 1 和记录行 r1 存储在一起,查找到索引就是查找到数据库记录。像 MySQL 数据库的主键就是聚簇索引,主键 ID 和所在的记录行存储在一起。MySQL 的数据库文件实际上是以主键作为两头节点,行记录作为叶子节点的一颗 B + 树。
另一种数据库索引是非聚簇索引,非聚簇索引在叶子节点记录的就不是数据行记录,而是聚簇索引,也就是主键,如下图。
通过 B + 树在叶子节点找到非聚簇索引 a,和索引 a 在一起存储的是主键 1,再依据主键 1 通过主键(聚簇)索引就能够找到对应的记录 r1,这种通过非聚簇索引找到主键索引,再通过主键索引找到行记录的过程也被称作回表。
所以通过索引,能够疾速查问到须要的记录,而如果要查问的字段上没有建索引,就只能扫描整张表了,查问速度就会慢很多。
数据库除了索引的 B + 树文件,还有一些比拟重要的文件,比方事务日志文件。
数据库能够反对事务,一个事务对多条记录进行更新,要么全副更新,要么全副不更新,不能局部更新,否则像转账这样的操作就会呈现重大的数据不统一,可能会造成微小的经济损失。数据库实现事务次要就是依附事务日志文件。
在进行事务操作时,事务日志文件会记录更新前的数据记录,而后再更新数据库中的记录,如果全副记录都更新胜利,那么事务失常完结,如果过程中某条记录更新失败,那么整个事务全副回滚,曾经更新的记录依据事务日志中记录的数据进行复原,这样全副数据都复原到事务提交前的状态,依然保持数据一致性。
此外,像 MySQL 数据库还有 binlog 日志文件,记录全副的数据更新操作记录,这样只有有了 binlog 就能够残缺复现数据库的历史变更,还能够实现数据库的主从复制,构建高性能、高可用的数据库系统。
总结
做利用开发须要理解 RDBMS 的架构原理,然而关系数据库系统十分宏大简单,对于个别的利用开发者而言,全面把握关系数据库的各种实现细节,代价昂扬,也没有必要。咱们只须要把握数据库的架构原理与执行过程,数据库文件的存储原理与索引的实现形式,以及数据库事务与数据库复制的基本原理就能够了。而后,在开发工作中针对各种数据库问题去思考,其背地的原理是什么,应该如何解决。通过这样一直地思考学习,岂但可能让应用数据库方面的能力一直进步,也能对数据库软件的设计理念也会有更粗浅的意识,本人软件设计与架构的能力也会失去增强。
本文由 mdnice 多平台公布