范式
范式是“符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度”。 符合高一级范式的设计,必定符合低一级范式,例如符合2NF的关系模式,必定符合1NF。
1NF:符合1NF关系中的每个属性(数据库中的每一列)都不可再分。对应我们设计就是不能出现重复的列。这个是关系数据库的基础没有人会犯这个错误(数据库也不让)。
2NF:在1NF的基础之上,消除了非主属性对于码的部分函数依赖。第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。
第二范式的意义:是为了消除冗余信息,若存在部分函数依赖,那么该属性是没必要存在这张表的,确定了被依赖的那个属性,就确定了该属性,没必要反复存在这张表中,新建一张表来存具有依赖关系的这两个属性就好了。
传递函数依赖:假如在『Y 不包含于 X,且 X 不函数依赖于 Y』这个前提下,Z 函数依赖于 Y,且 Y 函数依赖于 X ,那么我们就称 Z 传递函数依赖于 X ,记作 X T→ Z 。
候选码/码:设 K 为某表中的一个属性或属性组,若除 K 之外的所有属性都完全函数依赖于 K,那么我们称 K 为候选码,简称为码。在实际中我们通常可以理解为:假如当 K 确定的情况下,该表除 K 之外的所有属性的值也就随之确定,那么 K 就是码。包含在任何一个码中的属性成为主属性。主属性,候选码,都是唯一的意思(也就是数据库中的主键)。
3NF:在2NF的基础之上,消除了非主属性对于码的传递函数依赖。任意两个表,不能出现重复的非主键字段 。
第三范式的意义:符合3NF要求的数据库设计,基本上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。根据实体完整性的要求,主属性不能为空。
BCNF:在 3NF 的基础上,消除主属性对于码的部分与传递函数依赖,即为了应对存在多个码时,主属性之间的冗余问题。
总结:
1NF: 字段是最小的的单元不可再分 ;
2NF:满足1NF,表中的字段必须完全依赖于全部主键而非部分主键 (一般我们都会做到) ;
3NF:满足2NF,非主键外的所有字段必须互不依赖;
BCNF:满足3NF,消除表中的多值依赖 。
越高等级的范式所产生的表越多,而在应用程序使用的过程中越多的表Join和查询造成的性能损耗的问题,甚至很多情况下为了兼顾性能和开发我们甚至要做一下反范式的操作,所以一般认为超过第三范式都是多余的,所以再实际工作中不能太过教条!!!
数据库事务和隔离级别
数据库事务:
简单的说就是把对数据库的一系列操作放到一个可控制的过程中,进行可量化的控制。事务有4个必须的属性原则ACID:
▲原子性(Atomicity):必须保证在一个事务中的操作要么全部执行,要么全部不执行。
▲一致性(Consistency):事务操作在完成时必须使数据库保持一致的状态,内部数据结构必须是完整的。比如一个例子 A和B都有50块钱,他们两个无论怎么进行相互转账,A+B始终是100。
▲隔离性(Isolation):简单的来说就是各个事务之间的影响程度。
▲持久性(Durability):不管数据库是否发生问题,事务在完成之后数据应该永远的保存在数据库中。
事务之间是会相互影响的,这时候需要有一个规则来约束事务操作,于是有了事务隔离。
事务隔离级别(4种),由低到高依次为:
■Read uncommitted(读未提交) :避免了更新丢失,却可能出现脏读。也就是说事务B读取到了事务A未提交的数据 。
A进行了一条数据操作,但是没有提交事务,这时如果B进行这条数据查询,是可以查到A的数据操作的,但是这时候A还没有提交事务,如果A不提交或者进行了事务回滚,那么B查询到的数据就是脏数据。
■Read committed(读提交):避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了变化。
A正在进行数据操作,还没有提交事务,B这个时候也去查询了A操作的这个数据,并且修改了,马上提交了事务,这时候A再去提交事务的时候会发现数据不对了,因为A手上的数据是B没修改之前的,现在去提交时,数据库的数据已经变成B更改之后的了,这种场景发生在金钱上会有很大问题,明明卡上有钱的A去消费,结果B抢在A前面消费完了,这时候A就是没法付账了。这就是不可重复读。
■Repeatable read(重复读):避免了不可重复读和脏读,但是有时可能出现幻读。可以通过“共享读锁”和“排他写锁”实现。
这种情况只会发生在有一个事务在新增操作,比如A在查自己的银行卡现在是100块钱,A表示看花了,想再查一次,这时候有个人在给他转账,然后A第二次查询,查出了1000块了,又不敢相信自己是不是看花了。这就是幻读。
■Serializable(序列化,也叫串行化):事务只能一个接着一个地执行,但不能并发执行。 可以解决幻读问题 。
Serializable是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读。
对于大多数应用程序,可以优先考虑把数据库的隔离级别设置为read committed。可以避免脏读,而且有较好的并发性能。尽管可能导致不可重复读、幻读和第二类丢失更新这些并发问题。可以由应用程序采用悲观锁或乐观锁来控制。MySQL默认的隔离级别就是repeatable read。
为什么要锁、锁定分类、锁粒度
对于锁和锁粒度可以看下面的比喻:
为什么要加锁?加锁是为了防止不同的线程访问同一共享资源造成混乱。打个比方:人是不同的线程,卫生间是共享资源。你在上洗手间的时候肯定要把门锁上吧,这就是加锁,只要你在里面,这个卫生间就被锁了,只有你出来之后别人才能用。想象一下如果卫生间的门没有锁会是什么样?
什么是加锁粒度呢?所谓加锁粒度就是你要锁住的范围是多大。比如你在家上卫生间,你只要锁住卫生间就可以了吧,不需要将整个家都锁起来不让家人进门吧,卫生间就是你的加锁粒度。
怎样才算合理的加锁粒度呢?其实卫生间并不只是用来上厕所的,还可以洗澡,洗手。这里就涉及到优化加锁粒度的问题。
你在卫生间里洗澡,其实别人也可以同时去里面洗手,只要做到隔离起来就可以,如果马桶,浴缸,洗漱台都是隔开相对独立的,实际上卫生间可以同时给三个人使用,当然三个人做的事儿不能一样。这样就细化了加锁粒度,你在洗澡的时候只要关上浴室的门,别人还是可以进去洗手的。如果当初设计卫生间的时候没有将不同的功能区域划分隔离开,就不能实现卫生间资源的最大化使用。这就是设计架构的重要性。
为什么加锁?
如何保证数据并发访问的一致性、有效性是所在有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素,锁对数据库而言显得尤其重要,也更加复杂。防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决。
锁定机制分类:
锁定机制就是数据库为了保证数据的一致性而使各种共享资源在被并发访问,访问变得有序所设计的一种规则。MySQL数据库由于其自身架构的特点,存在多种数据存储引擎,每种存储引擎所针对的应用场景特点都不太一样,为了满足各自特定应用场景的需求,每种存储引擎的锁定机制都是为各自所面对的特定场景而优化设计,所以各存储引擎的锁定机制也有较大区别。
按封锁类型分类:(数据对象可以是表可以是记录)
1)排他锁:(又称写锁,X锁)
会阻塞其他事务读和写。若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对加任何类型的锁,知道T释放A上的锁。这就保证了其他事务在T释放A上的锁之前不能再读取和修改A。
2)共享锁:(又称读取,S锁)
会阻塞其他事务修改表数据。若事务T对数据对象A加上S锁,则其他事务只能再对A加S锁,而不能X锁,直到T释放A上的锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
X锁和S锁都是加载某一个数据对象上的。也就是数据的粒度。
按封锁的数据粒度分类如下:
1)行级锁定(row-level):开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。
缺陷:由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。
2)表级锁定(table-level):开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
和行级锁定相反,表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。
缺陷:锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并发度大打折扣。
3)页级锁定(page-level):MySQL特有,开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
页级锁定是MySQL中比较独特的一种锁定级别,在其他数据库管理软件中也并不是太常见。页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。
缺陷:页级锁定和行级锁定一样,会发生死锁。
对于不可重复读,只需采取行级锁防止该记录数据被更改或删除,然而对于幻读必须加表级锁,防止在这个表中新增一条数据。
乐观锁、悲观锁的概念和实现方式
常见的四种锁:
1、乐观锁:每次去找数据库拿数据都认为别人不会对数据做什么更改,所以不会上锁。但是在找数据库要数据的时候会带一个数据版本过来,做更新的时候会去把拿到的这个版本与数据库现在的要修改的数据版本做对比 只有大于或者等于数据库中的数据版本,那么它就认为这次操作是对的,可以执行。否则就不执行。
2、悲观锁(for update):每次去找数据库拿数据都认为别人会改,所以它在查询的时候就给数据加锁,其他人只能查询数据不能对数据做修改,只能等加锁的人操作完成之后才会释放锁。 乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。
3、共享锁:又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改 。
4、排它锁:又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
悲观锁,它是一种保守型锁,当一个事务获取到了一个数据的悲观锁之后,其他的事务就再也无法对该数据进行操作。也就是传统意义上的锁。 乐观锁不同于悲观锁,它的主要特点在于它更加宽松,它维持了一个version字段,如果一个事务获取到了乐观锁,那么在读取数据时,会连带着将这个version字段一起读出来。如果要对这个数据进行update操作,那么就会对内存中的version+1,再丢回数据库去进行比对,如果内存中的verison比数据库中的version字段大,那么就准许更新,否则就拒绝该更新操作。
MySQL、Oracle如何实现分页
mysql实现分页的方式: limit start(即起始行),count(即每一页的最大记录数)。参数值不能在语句当中写计算表达式,写到语句之前必须计算好值。 如果数据量比较小,10w以下,直接简单的使用这种方式就行了。
如果数据量比较大,特别是100w以上的数据量,用上面那种方式在最后的一些分页获取时特别慢,eg:limit 1200000,1000; LIMIT语句的偏移量就会越大,速度也会明显变慢。
改进方式:
每次获取主键id,并对结果集做一个id排序,where条件里加上id>上一次的最大id,limit可以写成固定的limit 0,2000
这样做的好处就是每次都会用到primary key,并且过滤掉之前已获取的数据,每次只需要获取最前面的2000条数据即可,效率大大提升,每次分页的耗时基本都是一样的。
SELECT id,product_id,merchant_id,chinese_name,english_name,
subtitle,is_deleted,is_available,create_time,CODE,
company_id,merchant_series_id,tax_no,TYPE,sale_type,
IF (datediff(NOW(), create_time) > 14,0,1) AS isNew
FROM merchant_product
WHERE is_deleted = 0 AND is_available = 1 AND is_vendible = 1 AND STATUS = 4 AND id > #maxId# order by id
LIMIT 0,2000;
如果后面要join其他表b的话,可以where a_id<=maxId and a_id>=minId
条件加上,然后b表的a_id字段有索引的话,查询速度就很快了。
Oracle:
select * from (
select rownum rn,a.* from table_name a where rownum <= x //结束行,x = startPage*pageSize
) where rn >= y; //起始行,y = (startPage-1)*pageSize+1
注:(1)>= y,<= x表示从第y行(起始行)~x行(结束行) 。
(2)rownum只能比较小于,不能比较大于,因为rownum是先查询后排序的,例如你的条件为rownum>1,当查询到第一条数据,rownum为1,则不符合条件。第2、3...类似,一直不符合条件,所以一直没有返回结果。所以查询的时候需要设置别名,然后查询完成之后再通过调用别名进行大于的判断。
用一张大表读取数据,如何解决性能问题
1.百万数据下查询方面:
先来看下MySQL中两种查询引擎查询速度:
InnoDB 中不保存表的具体行数,即执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多少行。MyISAM只要简单的读出保存好的行数即可。注意的是,当count(*)语句包含 where条件时,两种表的操作有些不同,InnoDB类型的表用count(*)或者count(主键),加上where col 条件。其中col列是表的主键之外的其他具有唯一约束索引的列,这样查询时速度会很快,也就是可以避免全表扫描。
总结:mysql 在300万条数据(myisam引擎)情况下使用 count(*) 进行数据总数查询包含条件(正确设置索引)运行时间正常。对于经常进行读取的数据,建议使用myIsam引擎。
2.百万数据下分页问题:
在开发过程中我们经常会使用分页,核心技术是使用limit进行数据的读取,在使用limit进行分页的测试过程中,得到以下数据:
select * from news order by id desc limit 0,10
耗时0.003秒
select * from news order by id desc limit 10000,10
耗时0.058秒
select * from news order by id desc limit 100000,10
耗时0.575秒
select * from news order by id desc limit 1000000,10
耗时7.28秒
mysql在数据量大的情况下分页起点越大查询速度越慢,100万条起的查询速度已经需要7秒钟。这是一个我们无法接受的数值!
改进方案1:
select * from news
where id > (select id from news order by id desc limit 1000000, 1)
order by id desc
limit 0,10
查询时间 0.365秒,提升效率是非常明显的!原理是什么呢?我们使用条件对id进行了筛选,在子查询 (select id from news order by id desc limit 1000000, 1) 中我们只查询了id这一个字段比起select * 或 select 多个字段节省了大量的查询开销!:
改进方案2(适合id连续的系统,速度极快;不适合带有条件的、id不连续的查询。):
select * from news
where id between 1000000 and 1000010
order by id desc
3.百万数据下,MySQL条件查询、分页查询的注意事项。
select id from news
where cate = 1
order by id desc
limit 500000 ,10
查询时间 20 秒,好恐怖的速度!利用上面分页查询优化方式1进行优化:
select * from news
where cate = 1 and id > (select id from news where cate = 1 order by id desc limit 500000,1 )
order by id desc
limit 0,10
查询时间 15 秒,优化效果不明显,条件带来的影响还是很大!在这样的情况下无论我们怎么去优化sql语句就无法解决运行效率问题。那么换个思路:建立一个索引表,只记录文章的id、分类信息,我们将文章内容这个大字段分割出去。
新建表 news2 [ 文章表 引擎 myisam 字符集 utf-8 ] ,两个字段:id int 11 主键 自动增加、cate int 11 索引。在写入数据时将2张表同步,查询是则可以使用news2 来进行条件查询:
select * from news
where cate = 1 and id > (select id from news2 where cate = 1 order by id desc limit 500000,1 )
order by id desc
limit 0,10
注意条件 id > 后面使用了news2 这张表! 运行时间 1.23秒,我们可以看到运行时间缩减了近20倍!!数据在10万左右是查询时间可以保持在0.5秒左右,是一个逐步接近我们能够容忍的值!
但是1秒对于服务器来说依然是一个不能接受的值!!还有什么可以优化的办法吗??我们尝试了一个伟大的变化:将 news2 的存储引擎改变为innodb,执行结果是惊人的!只需要 0.2秒,非常棒的速度。
https://www.cnblogs.com/llzhang123/p/9239682.html
内连接、左连接、右连接的作用及区别
- 左连接:左边有的,右边没有的为null,A LEFT JOIN B,连接查询的数据,在A中必须有,在B中可以有可以没有。
- 右连接:左边没有的,右边有的为null。与左连接相反。
- 内连接:显示左边右边共有的,即A INNER JOIN B ,在A中也有,在B中也有的数据才能查询出来。
补充:还有一中叫自连接,自连接(self join)可能看起来有点晦涩难懂,但是实际上换个角度你就会豁然开朗,你可以把它这个过程想象成两张一样的表进行左连接或右连接,这样就会简单多了,其中一张表通过设别名的方式成为了虚表,但是共享原标中的信息。应用场景是这样的,就是表的一个字段和另一个字段是相同性质的东西,譬如员工与上司,他们本质也都是员工,在员工表中,员工的直接上司编号会以另一个字段的形式出现,但是他的上司的编号也是会出现在员工编号这个字段里。那么在这种情况下,假如需要去查询某一位员工的上司的信息,在已知该员工编号的条件下,可以根据他的编号去获得上司的编号,进而通过上司的编号去获得上司的信息。
上面的表名为Employee,那么我要查询李四的上司的信息,对应的sql语句应该是这样的:
select *from Employee e1 left join Employee e2 on e1.empLeaderId=e2.empId where e1.empId=2;
Statement和PreparedStatement之间的区别
两者关系: PreparedStatement接口继承自Statement接口,以下是两者区别:
● PreparedStatement提高了代码的可读性和可维护性
PreparedStatement使用占位符,容易理解,可读性强,而使用Statement使用的字符串拼接,麻烦而且过长时可读性差。
● PreparedStatement性能更高
创建PreparedStatement对象时使用SQL语句做参数,会解析并编译SQL语句,也可以使用占位符“?”的SQL语句做参数,在通过setXxx()方法给占位符赋值后执行SQL语句时无须再解析和编译SQL语句,是直接执行的。当进行批处理(多次执行相同操作)时,效率高。而创建Statement对象不使用SQL参数,不会解析并编译SQL语句,每次调用执行SQL语句时都要进行SQL语句的解析和编译操作,效率低。
● PreparedStatement更安全,批处理效率高
PreparedStatement使用预处理编译,批处理比Statement效率高,传入的任何参数都不会和已经编译的SQL语句进行拼接,避免了SQL注入攻击问题。
索引以及索引的实现(B+树介绍,和B树、R树的区别)
索引是提高数据库表访问速度的方法,索引分为聚集索引和非聚集索引。聚集索引:对正文内容按照一定规则排序的目录;非聚集索引:目录按照一定顺序排序,正文按照另一种顺序排序,目录和正文之间保持一种映射关系。
打个比方就是:把数据库索引比作字典查询索引,聚集索引就是按照拼音查找,拼音栏中字的顺序就是查找得到的字的顺序。非聚集索引就像按照偏旁部首查找,同是单人旁查到的字所在的页码可能是杂乱的,没有顺序的。
B树是为磁盘或其他直接存储辅助存储设备而设计的一种平衡二叉查找树,B树与红黑树类似,但在降低磁盘I/O操作次数方面要更好一些,数据库就是通常用B树来进行存储信息。开始了解B~tree之前,先了解下相关的硬件知识,才能很好的了解为什么需要B~tree这种外存数据结构。
所以,在大规模数据存储方面,大量数据存储在外存磁盘中,而在外存磁盘中读取/写入块(block)中某数据时,首先需要定位到磁盘中的某块,如何有效地查找磁盘中的数据,需要一种合理高效的外存数据结构,就是下面所要重点阐述的B-tree结构,以及相关的变种结构:B+-tree结构和B*tree结构(B+树的升级版)。
B树:每个节点都存储key和data,所有节点组成这棵树,并且叶子节点指针为null。
B+树:只有叶子节点存储data,叶子节点包含了这棵树的所有键值,叶子节点不存储指针。所有非终端节点看成是索引,节点中仅含有其子树根节点最大(或最小)的关键字,不包含查找的有效信息。B+树中所有叶子节点都是通过指针连接在一起。
B*树:
为什么使用B+树?
1.文件很大,不可能全部存储在内存中,故要存储到磁盘上;
2.索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数(为什么使用B-/+Tree,还跟磁盘存取原理有关);
3. 局部性原理与磁盘预读,预读的长度一般为页(page)的整倍数,(在许多操作系统中,页得大小通常为4k)
4. 数据库系统巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样,每个节点只需要一次I/O 就可以完全载入,(由于节点中有两个数组,所以地址连续)。而红黑树这种结构, h明显要深的多。由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性。
为什么说B+-tree比B 树更适合实际应用中操作系统的文件索引和数据库索引?
★ B+-tree的磁盘读写代价更低
B+-tree的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶B-tree(一个结点最多8个关键字)的内部结点需要2个盘快。而B+ 树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B 树就比B+ 树多一次盘块查找时间(在磁盘中就是盘片旋转的时间)。
★ B+-tree的查询效率更加稳定
由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
在MySQL中,最常用的两个存储引擎是MyISAM和InnoDB,它们对索引的实现方式是不同的。MyISAM data存的是数据地址,索引是索引,数据是数据。InnoDB data存的是数据本身,索引也是数据。
R树是种处理高维空间存储问题的数据结构,R树在数据库等领域做出的功绩是非常显著的。它很好的解决了在高维空间搜索等问题。
举个R树在现实领域中能够解决的例子:查找20英里以内所有的餐厅。如果没有R树你会怎么解决?一般情况下我们会把餐厅的坐标(x,y)分为两个字段存放在数据库中,一个字段记录经度,另一个字段记录纬度。这样的话我们就需要遍历所有的餐厅获取其位置信息,然后计算是否满足要求。如果一个地区有100家餐厅的话,我们就要进行100次位置计算操作了,如果应用到谷歌地图这种超大数据库中,这种方法便必定不可行了。R树就很好的解决了这种高维空间搜索问题。它把B树的思想很好的扩展到了多维空间,采用了B树分割空间的思想,并在添加、删除操作时采用合并、分解结点的方法,保证树的平衡性。因此,R树就是一棵用来存储高维数据的平衡树。
R树是B树在高维空间的扩展,是一棵平衡树。每个R树的叶子结点包含了多个指向不同数据的指针,这些数据可以是存放在硬盘中的,也可以是存在内存中。根据R树的这种数据结构,当我们需要进行一个高维空间查询时,我们只需要遍历少数几个叶子结点所包含的指针,查看这些指针指向的数据是否满足要求即可。这种方式使我们不必遍历所有数据即可获得答案,效率显著提高。下图1是R树的一个简单实例:
我们在上面说过,R树运用了空间分割的理念,这种理念是如何实现的呢?R树采用了一种称为MBR(Minimal Bounding Rectangle)的方法,在此我把它译作“最小边界矩形”。从叶子结点开始用矩形(rectangle)将空间框起来,结点越往上,框住的空间就越大,以此对空间进行分割。有点不懂?没关系,继续往下看。在这里我还想提一下,R树中的R应该代表的是Rectangle(此处参考wikipedia上关于R树的介绍),而不是大多数国内教材中所说的Region(很多书把R树称为区域树,这是有误的)。我们就拿二维空间来举例。下图是Guttman论文中的一幅图:
我来详细解释一下这张图。先来看图(b)
- 首先我们假设所有数据都是二维空间下的点,图中仅仅标志了R8区域中的数据,也就是那个shape of data object。别把那一块不规则图形看成一个数据,我们把它看作是多个数据围成的一个区域。为了实现R树结构,我们用一个最小边界矩形恰好框住这个不规则区域,这样,我们就构造出了一个区域:R8。R8的特点很明显,就是正正好好框住所有在此区域中的数据。其他实线包围住的区域,如R9,R10,R12等都是同样的道理。这样一来,我们一共得到了12个最最基本的最小矩形。这些矩形都将被存储在子结点中。
- 下一步操作就是进行高一层次的处理。我们发现R8,R9,R10三个矩形距离最为靠近,因此就可以用一个更大的矩形R3恰好框住这3个矩形。
- 同样道理,R15,R16被R6恰好框住,R11,R12被R4恰好框住,等等。所有最基本的最小边界矩形被框入更大的矩形中之后,再次迭代,用更大的框去框住这些矩形。
用地图的例子来解释,就是所有的数据都是餐厅所对应的地点,先把相邻的餐厅划分到同一块区域,划分好所有餐厅之后,再把邻近的区域划分到更大的区域,划分完毕后再次进行更高层次的划分,直到划分到只剩下两个最大的区域为止。要查找的时候就方便了。
又以餐厅为例,假设我要查询广州市天河区天河城附近一公里的所有餐厅地址怎么办?
- 打开地图(也就是整个R树),先选择国内还是国外(也就是根结点)。
- 然后选择华南地区(对应第一层结点),选择广州市(对应第二层结点),
- 再选择天河区(对应第三层结点),
- 最后选择天河城所在的那个区域(对应叶子结点,存放有最小矩形),遍历所有在此区域内的结点,看是否满足我们的要求即可。
https://blog.csdn.net/peterchan88/article/details/52248714
什么是数据库连接池
创建数据库连接是一个很耗时的操作,也容易对数据库造成安全隐患。所以,在程序初始化的时候,集中创建多个数据库连接,并把他们集中管理,供程序使用,可以保证较快的数据库读写速度,还更加安全可靠。创建连接池,可以减少对象初始化、创建的时间,这个和线程池有点类似。都是为了节省创建时间,资源复用。
刚接触JDBC时,都是通过DriverManager.getConnection这个接口方法来获得连接对象Connection。但是每次都通过这种方式来获得数据库连接对象很麻烦也很耗费资源。每次需要连接对象的时候,都要花费时间通过这个方法去获得数据库连接对象,很耗费时间。 怎么优化获得数据库连接对象使耗时更短?
使用数据库连接池:就是在连接数据库前,事先用几个数据库连接对象变量去和数据库进行连接好(一直连接,不断开),在池子中一直等待,当要使用数据库连接对象的时候,直接到池子中把已经提前连接好的对象拿来用,然后用完之后再把对象还回池子里面去。如果池子中有3个事先连接好的数据库连接对象,但是一次把三个对象都拿去用了,那么下一个用户过来要使用数据库连接对象的时候,就要等待了,等待前面的用完把对象还回到池子中,然后再去拿来使用。连接池的目的就是提前准备多个已经连接到了数据库的对象在那里准备着被拿去使用,当你要使用的时,直接从池子里拿出来用,用完之后再给人家放回去,用来优化数据库连接。
数据库连接池使用过程分为四步:程序初始化时创建连接池→使用时向连接池申请可用连接→使用完毕,将连接返还给连接池→ 程序退出时,断开所有连接,并释放资源。
Sun公司并没有做数据库连接池的实现,它只是做了一个规范,也不给出具体的数据库连接池的概念,而是做了一个数据源javax.sql.DataSource,这个数据库源只是数据库连接的一个优化手段的规范的接口,至于你具体怎么去实现这个数据库连接池由自己定义。或者你自己不想使用数据库连接池这个方法也可以,你只要在这个地方能够以很优化的方式给我提供数据库连接对象就行了。
连接池框架:
★ C3P0 比较耗费资源,效率方面可能要低一点。
★ DBCP 在实践中存在BUG,在某些种情会产生很多空连接不能释放,Hibernate3.0已经放弃了对其的支持。
★ Proxool 的负面评价较少,现在比较推荐它,而且它还提供即时监控连接池状态的功能,便于发现连接泄漏的情况。
★ Druid 阿里出品,淘宝和支付宝专用数据库连接池,它结合了C3P0、DBCP、PROXOOL等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池。但它不仅仅是一个数据库连接池,它还包含一个ProxyDriver,一系列内置的JDBC组件库,一个SQL Parser。支持所有JDBC兼容的数据库,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等等。Druid针对Oracle和MySql做了特别优化,比如Oracle的PS Cache内存占用优化,MySql的ping检测优化。Druid提供了MySql、Oracle、Postgresql、SQL-92的SQL的完整支持,这是一个手写的高性能SQL Parser,支持Visitor模式,使得分析SQL的抽象语法树很方便。