做利用开发的同学经常感觉数据库由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多平台公布