Java复习系列02-数据库开发
Table of Contents
1 数据库优化
数据库的优化分为好多个层次
- 语句上的优化
- 尽量不要使用多表查询、尽量不要使用各种神奇的统计查询、如果需要使用的时候建议使用子查询来代替
- 使用索引
- 空间换时间、时间换空间,是不是频繁的写操作。
- 读写分离
- 分库分表
- 引入搜索引擎
2 NoSQL使用场景
nosql可以实现文档存储、缓存、图像存储。
- 文档型:替换传统开发中的多表查询,把结果当成一个文档存储下来,例如mongoDb
- 缓存型:存储数据,如验证码,例如redis
3 分布式数据库
对数据库做集群,会有同步问题,比较好的是读写分离和分库分表,以及对高频数据做缓存,对缓存数据库做集群。流量的的时候要考虑引入大数据平台和分析系统。
4 数据库设计
避免使用存储过程
5 缓存
读取数据的一般流程,将硬盘上的数据加载到内存后再通过内存进行读取。因为硬盘上所保存的都是物理上的二进制数据。大量人访问的时候如果通过数据库访问(硬盘上读取)速度就会很慢。换固态硬盘速度会有所提高。但还是有内存与内存间读取的延时。
不读取硬盘上的内容,速度就很快了。比如直接使用内存进行缓存。缓存的核心意义,为了减少数据库的操作。
6 缓存与数据库同步
思考
- 缓存是否数据库专用的缓存
- 缓存是否进行分布式或反向代理的时候使用的(cdn)
- 缓存是否采用缓存数据库(如redis)
解决方案
- 对强一致要求比较高的,应采用实时同步方案,即查询缓存查询不到再从DB查询,保存到缓存;更新缓存时,先更新数据库,再将缓存的设置过期(建议不要去更新缓存内容,直接设置缓存过期)。
- 对于并发程度较高的,可采用异步队列的方式同步,可采用kafka等消息中间件处理消息生产和消费。
- 使用阿里的同步工具canal,canal实现方式是模拟mysql slave和master的同步机制,监控DB bitlog的日志更新来触发缓存的更新,此种方法可以解放程序员双手,减少工作量,但在使用时有些局限性。
- 采用UDF自定义函数的方式,面对mysql的API进行编程,利用触发器进行缓存同步,但UDF主要是c/c++语言实现,学习成本高。
7 乐观锁和悲观锁
悲观锁的本质是数据库自身所具备的一种处理机制,在数据库的事务里面有个隔离型的概念,其主要主要特征就是当一个session操作某条数据额时候,其它的session是无法操作该数据的,这是数据库自身具备的。
乐观锁不使用数据库自身的特性,利用辅助字段版本号version实现。
8 数据缓存
所有的程序执行最终都需要CPU完成,但是CPU不能够直接操作硬盘文件系统,而是要把数据先从硬盘写入内存,这个时候就有寻址的问题,缓存就是用来解决内存和硬盘文件系统转换的问题。
关于缓存的思考?
- 哪些数据需要保存在缓存里?
- 热点、高频
- 不用的数据什么时候清理掉?
- JVM GC 4中引用类型
- 常用算法:LRU、FIFO
- 是否需要同步?
- 一般是只读的模式,不会进行更新
缓存的形式
- 文件缓存:将一些数据保存在文件里面,避免动态生成
- 内存缓存:将数据缓存在内存中
- 缓存数据库上(Nosql数据库):redis
9 数据库更新监听和数据不一致的问题
9.1 误区
- 使用触发器
- 定期进行数据库的检测
9.2 如何保证缓存与数据库的双写一致性?
一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统不是严格要求 “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:读请求和写请求串行化,串到一个内存队列里去。
串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。
- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
- 更新的时候,先更新数据库,然后再删除缓存。
问题:先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。
解决思路:先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。
比较复杂的数据不一致问题分析
数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。随后数据变更的程序完成了数据库的修改。完了,数据库和缓存中的数据不一样了…
解决方案如下:
更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个 jvm 内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部队列中。
一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,没有读到缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。
这里有一个优化点,一个队列中,其实多个更新缓存请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。
高并发的场景下,该解决方案要注意的问题:
- 读请求长时阻塞
由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。
该解决方案,最大的风险点在于说,可能数据更新很频繁,导致队列中积压了大量更新操作在里面,然后读请求会发生大量的超时,最后导致大量的请求直接走数据库。务必通过一些模拟真实的测试,看看更新数据的频率是怎样的。
- 读请求并发量过高
这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能扛的住,需要多少机器才能扛住最大的极限情况的峰值。
但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。
- 多服务实例部署的请求路由
可能这个服务部署了多个实例,那么必须保证说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 Nginx 服务器路由到相同的服务实例上。
比如说,对同一个商品的读写请求,全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的 hash 路由,也可以用 Nginx 的 hash 路由功能等等。
- 热点商品的路由问题,导致请求的倾斜
万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大。就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以其实要根据业务系统去看,如果更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些。
10 数据库连接池实现原理
对于共享资源,有一个很著名的设计模式:资源池(resource pool)。该模式正是为解决资源频繁分配、释放所造成的问题。数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需要从缓冲池中取出一个了,使用完毕后再放回去。我们可以通过设定连接池最大数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库连接使用数量,使用情况,为系统开发,测试以及性能调整提供依据。
10.1 连接池的相关问题分析
- 并发问题
- 为了使连接管理服务具有最大的通用性,必须考虑多线程环境,并发问题。使用synchronized(java) lock(c#)等关键字确保线程同步。
- 事务问题
- 事务具有原子性,为此我们可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务管理的复杂性。
- 连接池的分配和释放
- 连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
- 对于连接的管理可使用一个List。即把已经创建的连接都放入List中去统一管理。
- 连接池的配置和维护
- 连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minConnection)和最大连接数(maxConnection)等参数来控制连接池中的连接。
- 如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。
10.2 引用记数
在分配、释放策略对于有效复用连接非常重要,我们采用的方法也是采用了一个很有名的设计模式:reference counting(引用记数)。该模式在复用资源方面使用非常广泛,我们把该方法运用到对于连接分配释放上。每一个数据库连接,保留一个引用记数,用来记录该链接的使用者的个数。具体实现上,我们对connection类进行了进一步包装来实现引用记数。被包装的connection类我们提供2个方法来实现引用记数的操作,一个是repeat(被分配出去)一个是remove(被释放回来);然后利用repeatnow属性来确定当前引用多少,具体是哪个用户引用了该连接,将在连接池中登记;最后提供isRepeat属性来确定该连接是否可以使用引用记数技术。一旦一个连接被分配出去,那么就会对该连接的申请者进行登记,并且增加引用记数,当被释放回来时就删除他登记的信息,同时减少一次引用记数。这样做的一个很大的好处是,使得我们可以高效的使用连接,因为一旦所有连接都被分配出去,我们就可以根据相应的策略从使用池中挑出一个正在使用的连接来复用,而不是随便拿出一个连接去复用。
连接池用于创建和管理数据库连接的缓冲技术,缓冲池中的连接可以被任何需要他们的线程使用。当一个线程需要使用JDBC对一个数据库操作时,将从池中请求一个连接。当这个链接使用完毕后,将返回连接池中,等待为其他的线程服务。
10.3 连接池的主要优点
- 减少连接的创建时间,连接池中的连接是已准备好的,可以重复使用的,获取后可以直接访问数据库,因此减少了连接创建的次数和时间。
- 简化的编程模式。当使用连接池时,每一个单独的线程能够像创建自己的JDBC连接一样操作,允许用户直接使用 JDBC编程技术。
- 控制资源的使用。如果不使用连接池,每次访问数据库都需要创建一个连接,这样系统的稳定性受系统的连接需求影响很大,很容易产生资源浪费和高负载异常。连接池能够使性能最大化,将资源利用控制在一定的水平之下。连接池能控制池中的链接数量,增强了系统在大量用户应用时的稳定性。
10.4 连接池的工作原理
连接池的核心思想是连接的复用,通过建立一个数据库连接池以及一套连接使用、分配和管理策略,使得该连接池中的连接可以得到高效,安全的复用,避免了数据库连接频繁建立和关闭的开销。
连接池的工作原理主要由三部分组成,分别为连接池的建立,连接池中连接的使用管理,连接池的关闭。
第一、连接池的建立。一般在系统初始化时,连接池会根据系统配置建立,并在池中建立几个连接对象,以便使用时能从连接池中获取,连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。java中提供了很多容器类,可以方便的构建连接池,例如Vector,stack等。
第二、连接池的管理。连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其策略是:
当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有控线连接,则查看当前所开的连接数是否已经达到最大连接数,例如如果没有达到就重新创建一个请求的客户;如果达到,就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。
当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过了就从连接池中删除该连接,否则就保留为其他客户服务。该策略保证了数据库连接的有效复用,避免了频繁建立释放连接所带来的系统资源的开销。
第三、连接池的关闭。当应用程序退出时,关闭连接池中所有的链接,释放连接池相关资源,该过程正好与创建相反。
10.5 注意事项
- 数据库连接池的最小连接数是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费。
- 数据库连接池的最大连接数是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求将被加入到等待队列中,这会影响之后的数据库操作。
- 最大连接数具体值要看系统的访问量.要经过不断测试取一个平衡值
- 隔一段时间对连接池进行检测,发现小于最小连接数的则补充相应数量的新连接
- 最小连接数与最大连接数差距,最小连接数与最大连接数相差太大,那么最先的连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接。不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,它将被放到连接池中等待重复使用或是空闲超时后被释放。
11 数据库事务控制
事务逻辑上的一组对数据对操作,组成这些操作的各个逻辑单元,要么一起成功,要么一起失败。
本文以Spring事务为例。
11.1 事务特性(ACID4种)
- 原子性(atomicity):强调事务的不可分割;
- 一致性(consistency):事务的执行前后数据的完整性保持一致;
- 隔离性(isolation):一个事务的执行的过程中,不应该受到其他事务的干扰;
- 持久性(durability):事务一旦结束,数据就持久到数据库。
11.2 置事务隔离级别(4种)
如果不考虑隔离性引发的安全性问题:
- 脏读:一个事务读到了另一个事务未提交的数据
- 不可重复读:一个事务督导另一个事务已经提交的update的数据导致多次查询结果不一致
- 虚幻读:一个事务读到了另一个事务已经提交的insert的数据导致多次查询结果不一致
解决读问题:设置事务隔离级别(4种)
DEFAULT这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别;
- 未提交读(read uncommited):脏读,不可重复读,虚读都有可能发生;
- 已提交读(read commited):避免脏读。但是不可重复读和虚读都有可能发生;
- 可重复读(repeatable read):避免脏读和不可重复读,但是虚读有可能发生;
- 串行化的(serializable):避免以上所有读问题。
MySQL默认:可重复读,Sql server和Oracle默认:已提交读
read uncommitted:是最低读事务隔离级别,它允许另外一个事务可以看到这个事务未提交读数据。
read commited: 保证一个事务提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
repeatable read:这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻想读。它除了保证一个事务不能被另外一个事务读取未提交读数据之外还避免了一下情况产生(不可重复读)。
serializable:这是花费最高代价但最可靠但事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻象读(避免三种)。
11.3 spring事务的传播行为(propagation behavior 7种)
指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
例如:在一个类中methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA中继续执行呢,还是为自己开启一个新事务执行,这就是由methodB的事务传播行为决定的。注:methodA和methodB在同一个类中时,需要从上下文容器中重新获取一次bean,再调methodB即可。
在Transaction Definition接口中定义了7种事务得传播行为:h
保证同一个事务中
- PROPAGATIONREQUIRED支持当前事务,如果不存在,就新建一个(默认)
- PROPAGATIONSUPPORTS支持当前事务,如果不存在,就不适用事务
- PROPAGATIONMANDATORY 支持当前事务,如果不存在,抛出异常
保证没有在同一个事务中
- PROPAGATIONREQUIRESNEW如果有事务存在,挂起当前事务,创建一个新的事务
- PROPAGATIONNOTSUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
- PROPAGATIONNEVER 以非事务方式运行,如果有事务存在,抛出异常
- PROPAGATIONNESTED 如果当前事务存在,则嵌套事务执行
11.4 业务层捕获异常(特殊需要)
最近遇到这样的问题,使用spring时,在业务层需要捕获异常(特殊需要),当前一般情况下不需要这样做的。
在ServiceA类中有method1,在该方法中调用另一个ServiceB类的method2方法时(假定该方法抛出异常),method1方法捕获该异常但是没有向上抛出。spring提示:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
出现以上原因是spring执行method1方法时,开启一个新的事物,当调用method2方法时,当method2方法加入到method1方法的当前事务,当method2抛出异常,spring标记事务为回滚状态,method1方法捕获该异常,但没有抛出,spring会提交method1的事务,但spring发现该事务已经标记为回滚状态,而代码中却要提交事务,所以才会抛出以上的异常。
解决的方法:在ServiceB的method2方法上注解,告诉spring当该方法抛出自定义异常CustomException时,不要回滚事务,这样当该方法抛出异常时,spring不会标记事务为回滚状态。
@Transactional(noRollbackFor=CustomException.class) public void method2() throws CustomException{ }
11.5 开发中常见的三种事务处理模式
- JDBC的原始处理,她提供有:commit()、rollback()等
- 容器事务控制,使用jta组件,一般不用
- Spring的事务控制
11.6 分布式事务
11.6.1 什么是分布式事务
分布式事务就是指事务的资源分别位于不同的分布式系统的不同节点之上的事务;
11.6.2 分布式事务产生的原因
- 数据库分库分表
在单库单表场景下,当业务数据量达到单库单表的极限时,就需要考虑分库分表,将之前的单库单表拆分成多库多表;
分库分表之后,原来在单个数据库上的事务操作,可能就变成跨多个数据库的操作,此时就需要使用分布式事务;
- 业务服务化
业务服务化即业务按照面向服务(SOA)的架构拆分整个网站系统;
比如互联网金融网站SOA拆分,分离出交易系统、账务系统、清算系统等,交易系统负责交易管理和记录交易明细,账务系统负责维护用户余额,所有的业务操作都以服务的方式对外发布;
一笔金融交易操作需要同时记录交易明细和完成用户余额的转账,此时需要分别调用交易系统的交易明细服务和账务系统的用户余额服务,这种跨应用、跨服务的操作需要使用分布式事务才能保证金融数据的一致性;
11.6.3 分布式事务原理简介
- ACID
1、原子性(Atomicity)
整个事务中的所有操作,要么都成功,要么都失败,不可能部分操作成功部分操作失败;
2、一致性(Consistency)
事务必须使数据库的数据从一个一致性状态变换到另外一个一致性状态。
3、隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
4、持久性(Durability)
在事务成功以后,该事务对数据库所做的更改应当持久的保存在数据库中;
- 2PC
两阶段提交协议(Two Phase Commitment Protocol)是分布式事务的基础协议。
在此协议中,一个事务协调器(TM, transaction manager)协调多个资源管理器(RM, resource manager)的活动;在一阶段所有资源管理器(RM)向事务管理器(TM)汇报自身活动状态,在第二阶段事务管理器(TM)根据各资源管理器(RM)汇报的状态,来决定各RM是执行提交操作还是回滚操作;
- 应用程序向事务管理器(TM)提交请求,发起方分布式事务;
- 一阶段,事务管理器(TM)联络所有资源管理器(RM),通知它们执行准备操作;
- 资源管理器(RM)返回准备成功,或者失败的消息给TM(响应超时算作失败);
- 二阶段,如果所有RM均准备成功,TM会通知所有RM执行提交;如果任一RM准备失败,TM会通知所有RM回滚;
通过事务管理器2阶段协调资源管理器,使所有资源管理器的状态最终都是一致的,要么全部提交,要么全部回滚。
- 2PC应用之XA
XA是X/Open组织提出的,定义了事务管理器与资源管理器之间通信的接口协议;XA协议由数据库实现,目前支持XA协议的数据库有Oracle、MySql、BD2等;
XA定义了一系列的接口:
- xastart: 启动XA事务
- xaend: 结束XA事务
- xaprepare: 准备阶段,XA事务预提交
- xacommit: 提交XA事务
- xarollback: 回滚XA事务
一个数据库实现XA协议之后,它就可以作为作为一个资源管理器参与到分布式事务中;
在一阶段,事务管理器协调所有数据库执行XA事务(xastart、用户SQL、xaend),并完成XA事务预提交(xaprepare);
在二阶段,如果所有数据库上XA事务预提交均成功,那么事务管理器协调所有数据库提交XA事务(xacommit);如果任一数据库上XA是我预提交失败,那么事务管理器会协调所有数据组回滚XA事务(xarollback);
- 2PC应用之TCC
TCC是Try、Confirm、Cancel 3个操作的缩写;
Try操作对应2PC的一阶段Prepare,Confirm对应2PC的二阶段commit,Cancel对应2PC的二阶段rollback;
这3个操作均有用户编码实现;
TCC三个操作描述:
- Try: 检测、预留资源;
- Confirm: 业务系统执行提交;默认Confirm阶段是不会出错的,只要TRY成功,CONFIRM一定成功;
- Cancel: 业务取消,预留资源释放;
用户通编码实现TCC并发布成服务,这个TCC服务就可以作为资源参与到分布式事务中;事务管理器分2阶段协调所有的TCC资源,使得所有TCC资源状态最终都是一致,要么全部提交,要么全部回滚;
TCC自编码的特性决定TCC资源管理器可以跨DB、跨应用实现资源管理,将对不同的DB访问、不同的业务操作通过编码方式转换一个原子操作,解决了复杂业务场景下的事务问题;
同时TCC的每一个操作对于DB来讲都是一个本地DB事务,操作结束则本地DB事务结束,数据库的资源也就被释放;这就规避了数据库层面的2PC对资源占用导致的性能低下问题;
11.6.4 柔性事务
单数据库事务完全遵循ACID规范,属于刚性事务,分布式事务要完全遵循ACID规范比较困难, 分布式事务属于柔性事务,满足BASE理论;
BASE描述: BA(Basic Availability 基本业务可用性)、S(Soft state 柔性状态)、E(Eventual consistency 最终一致性);
柔性事务对ACID的支持:
- 原子性:严格遵循;
- 一致性:事务完成后的一致性严格遵循,事务中的一致性可适当放宽;
- 隔离性:并行事务间不可影响;事务中间结果可见性允许安全放宽;
- 持久性:严格遵循;
为了可用性、性能的需要,柔性事务降低了一致性(C)与隔离性(I) 的要求,即“基本可用,最终一致”;
11.6.5 柔性事务的分类
柔性事务分为:两阶段型、补偿型、异步确保型、最大努力通知型;
- 两阶段型
就是分布式事务两阶段提交,对应技术上的XA、JTA/JTS,这是分布式环境下事务处理的典型模式。
- 补偿型
TCC型事务(Try/Confirm/Cancel)可以归为补偿型;TCC思路是:尽早释放锁;在Try成功的情况下,如果事务要回滚,Cancel将作为一个补偿机制,回滚Try操作;
TCC各操作事务本地化,且尽早提交 (放弃两阶段约束);当全局事务要求回滚时,通过另一个本地事务实现“补偿”行为;
TCC是将资源层的两阶段提交协议转换到业务层,成为业务模型中的一部分;
- 异步确保型
将一些同步阻塞的事务操作变为异步的操作,避免对数据库事务的争用;比如消息事务机制;
- 最大努力通知型
通过通知服务器(消息通知)进行,允许失败,有补充机制;
12 数据源
数据库的数据源的设置(DataSource)它存在的目的是为了解决数据库的频繁打开与关闭所带来的性能损耗。所以这种不进行重复打开与关闭,以及控制连接人数的做法就称为数据源。
数据源配置的两种方式:
- 容器配置(Tomcat、WebLogic、WAS)
- 程序配置(Spring中使用C3p0)
现在的开发基本上都会考虑使用程序配置了,旧时代程序配置,只是实现了数据库连接池的处理,新时代,例如阿里druid数据源,它可以实现监控操作。
Spring中提供了4种不同形式的数据源配置方式:
- Spring自带的数据源(DriverMangerDataSource);
- DBCP数据源;
- C3P0数据源;
- JNDI数据源。
不管如何处理,数据源都是为了提升数据库操作性能做的一种方案,本质的核心思想;避免了数据库频繁的打开和关闭的处理。
13 随机取得数据库数据
从数据库中(mysql)随机获取几条数据很简单,但是如果一个表的数据基数很大,比如一千万,从一千万中随机产生10条数据,那就相当慢了。
通过mysql最大值函数max(),最小值min()来减小查询消耗
因为mysql数据的最大值和最小值的获取几乎是0消耗的,数据库表数据与自身主键ID的集合做join查询,是非常快速的。刚我也试了一下,在16万数据的基数下,随机产生5条数据几乎看不到时间消耗的。但是这种查询方法有一个缺点,产生的数据ID都是连续的,如果想不连续,还要做循环执行才行(每循环一次随机1条或2条数据)。
SELECT sh1.* FROM fw_share AS sh1 JOIN (SELECT ROUND( RAND() * ((SELECT MAX(share_id) FROM fw_share)-(SELECT MIN(share_id) FROM fw_share)) + (SELECT MIN(share_id) FROM fw_share) ) AS share_id ) AS sh2 WHERE sh1.share_id>=sh2.share_id
其实通过有根据的随机产生,通过有索引的其它字段,如通过标题等,缩小范围进行随机。