原理:类似游标+ top 函数分页
代码可读性比较差下面是分析:
分析1,假如有100W条的数据
分析2,假设10W条位置处的id是100001,假设20W条位置处的id是200001,假设50W条位置处的id是500001
分析3,缓存10W,20W...90W,把这些位置的id缓存起来,sql语句作为key把10w位置的id 100001,20w位置的id 200001,…50w位置的id 500001...这些位置的id位置数据缓存起来,使用sql作为key是因为不同的sql语句缓存的id位置也不同
总结,如果每页10条数据,那么就有10W页,如果翻到5.1W页,数据肯定就是在50W位置之后,sql查询的时候直接从50W条数据开始往后查找,时间复杂度100W/10个缓存点=10W的数据的复杂度,如果有100个缓存点那么就是1W条的时间复杂度,如果翻页刚好在缓存点之后的几页复杂度接近于0,如果在下一个缓存点之前时间复杂度相当于1W。平均算来相当于5k的时间复杂度。这样的效率可想而知
海量数据分页(100W条),首次分页大概为10秒左右。以后翻页基本上是瞬间完成。要求是无排序情况下。
目前sybase分页:
1,hibernate分页:hibernate分页前几页和后几页没问题,如果数据量大翻到30000页时就内存溢出了。而且效率慢。
2,存储过程分页:使用存储过程分页大数据量也不适合。效率低而且不支持并发。
jdbc分页好处:
1,效率高。
2,所有数据库通用。
3,支持大数据。
jdbc分页要解决2个问题。
1,取分页总数
当没次翻页的时候都要这个数据,所以要把这个数据缓存起来(所以前面提到首次要10秒,这个要占用一部分时间,下次翻页就不用了)
2,取当前页数据
如何取这个是重点,这个也要分2步骤
1)找到当前页第一条数据的位置
这一步也是关键。如果你每次从第一条找,那么找第50W条记录会浪费很长。解决这个问题也要使用缓存。假如50W条数据增加50个缓存点。那么翻到最后一页就从第50个缓存点开始查找,也就是说从第49W条开始查找。那么无论翻倒哪一页最差效率为1W条的效率。(这里有一个要求就是通过id排序,所以这个算法不支持其他字段排序)
2)把这一页的数据取出来。
点击打开链接源代码2.0下载,修改部分BUG
地方
分页代码
/**
* jdbc分页add by wangmeng 2013-4-18
* 要求单表,无子查询,无关联查询
* @param sql 执行sql语句
* @param cls 封装数据表
* @param id id列名
* @param startNum 从哪条开始。0...n
* @param pageSize 每页条数
* @return
*/
public Page findPageBySql( final String sql, Class cls,final String id, int startNum,
final int pageSize) {
final Page page = new Page();//分页信息记录总数和当前页数据
try{
String execsql = sql;
String sql2 = sql.toLowerCase();
long btime = System.currentTimeMillis();
long etime ;
final Connection con = JDBCUtil.getConnection();
PreparedStatement stmt;
ResultSet rs ;
String counthql=sql2; //计算count(*)的SQL
int cacount = CacheUtil.getTotalSize(sql);//读取总数缓存
int total = 0;//返回count数
if(cacount == -1){//没有缓存
if(counthql.indexOf("order")>-1){
counthql="select count(*) "+counthql.substring(counthql.indexOf("from"), counthql.lastIndexOf("order"));
}else{
counthql="select count(*) "+counthql.substring(counthql.indexOf("from"), counthql.length());
}
System.out.println(counthql);
btime = System.currentTimeMillis();
stmt = con.prepareStatement(counthql);
rs = stmt.executeQuery();
rs.next();
page.setTotalCount(rs.getInt(1));
total = rs.getInt(1);
CacheUtil.setTotal(sql, total);
}else{
total = (Integer)cacount;
page.setTotalCount(total);
}
if(total <=0){
return page;
}
etime = System.currentTimeMillis();
System.out.println("countsql处理时间:"+(etime - btime));
btime = System.currentTimeMillis();
if(total < 1000){//小数据量处理
System.out.println(execsql);
stmt = con.prepareStatement(execsql);
rs = stmt.executeQuery();
int var = 0;
while(var++<startNum && rs.next());
List list = CloneUtil.cloneResultSet2List(rs,cls,null,pageSize);
page.setData(list);
}else{//大数据量处理
String t = sql2.substring(sql2.indexOf("from")+5);//获取表名
String idsql = "select "+id+" from "+t.trim().split(" ")[0]+" "; //先查询id位置的sql
if(sql2.contains("where")){//拼where子句
if(sql2.contains("order by")){
idsql += sql2.substring(sql2.indexOf("where"),sql.indexOf("order by"));
}else{
idsql += sql2.substring(sql2.indexOf("where"));
}
}
int orderIndex = sql2.indexOf("order by");
final String cachidsql = idsql;
if(orderIndex == -1){//无排序可以增加缓存进行快速查找
if(CacheUtil.isInitIndex(sql)){//有缓存使用缓存
Entry<Integer, Object> entry = CacheUtil.getFloorEntry(sql, startNum);
if(entry == null){
}
startNum -= entry.getKey();
if(idsql.contains("where")){
idsql +=" and "+id+" >= "+entry.getValue();
}else{
idsql +="where "+id+" >= "+entry.getValue();
}
idsql = "select top "+(startNum+pageSize)+idsql.substring(idsql.indexOf("select")+6);
}else{//没缓存增加
new Thread(){
@Override
public void run() {
try {
Connection c = JDBCUtil.getConnection();
ResultSet rs = c.prepareStatement(cachidsql+ " order by "+id).executeQuery();
int i =0;
int cap = CacheUtil.getIndexSize(sql);
Map map = new HashMap();
while(rs.next() ){
if(i % cap ==0){
map.put(i, rs.getInt(1));
}
i++;
}
CacheUtil.initPageIndex(sql, map);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
idsql += " order by "+id;
}
System.out.println(idsql);
stmt = con.prepareStatement(idsql);
rs = stmt.executeQuery();
int var = 0;
while(var++<startNum && rs.next());
int i = 0;
List ids = new ArrayList();
while(rs.next() && i++ < pageSize){//把缓存数据取出来
ids.add(rs.getObject(1));
}
etime = System.currentTimeMillis();
System.out.println("idsql处理时间:"+(etime - btime));
btime = System.currentTimeMillis();
StringBuilder sbsql = new StringBuilder();
if(orderIndex == -1){//无排序使用id>=?方式
sbsql.append(id).append(">=").append(ids.get(0));
execsql = "select top "+pageSize+execsql.substring(execsql.toLowerCase().indexOf("select")+6);
}else{//有排序使用id=? or id=?
sbsql.append(" (");
for (int j = 0; j < ids.size(); j++) {
if(sbsql.indexOf("("+id) != -1){
sbsql.append(" or ");
}
sbsql.append(id +" = ").append(ids.get(j));
}
sbsql.append(")");
}
if(!execsql.toLowerCase().contains("where")){
execsql += "where "+sbsql.toString();
}else {
execsql += " and "+sbsql.toString();
}
System.out.println(execsql);
QueryBySqlResultSet qbc = new QueryBySqlResultSet(execsql,cls,pageSize);//使用hibernate取数据
// stmt = con.prepareStatement(execsql);
// rs = stmt.executeQuery();
List list = (List)getHibernateTemplate().execute(qbc);
etime = System.currentTimeMillis();
System.out.println("查询数据时间:"+(etime - btime));
page.setData(list);
}
btime = System.currentTimeMillis();
new Thread(){
public void run() {
try {
JDBCUtil.closeConnection(con);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
}.start();
etime = System.currentTimeMillis();
System.out.println("rs关闭时间:"+(etime - btime));
}catch(Exception e){
e.printStackTrace();
}
return page;
}
CacheUtil缓存工具类
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.metarnet.eoms.common.base.model.SQLCacheInfo;
/**
* 给数据库表记录数和Id增加缓存
* @author wangmeng
*
*/
public class CacheUtil {
private static LinkedHashMap<String,Object> lmap = new LinkedHashMap<String,Object>(){
private static final long serialVersionUID = -3432076593791024110L;
//创建一个LinkedHashMap匿名内部类最大size是30超过30自动删除第一个
private final static int MAX_SIZE = 30;
protected boolean removeEldestEntry(java.util.Map.Entry<String,Object> eldest) {
return size()>MAX_SIZE;
};
};
private CacheUtil(){};
/**
* 缓存总数
* @param key
* @param value
*/
public static void setTotal(String key,int value){
SQLCacheInfo info = (SQLCacheInfo)lmap.get(key);
if(info == null){
info = new SQLCacheInfo();
}
info.setTotalSize(value);
lmap.put(key, info);
}
public static int getTotalSize(String key){
SQLCacheInfo info = (SQLCacheInfo)lmap.get(key);
if(info == null){
return -1;
}
return info.getTotalSize();
}
/**
* 缓存id位置
* @param key
* @param map
*/
public static void initPageIndex(String key, Map<Integer,Object> map){
SQLCacheInfo info = (SQLCacheInfo)lmap.get(key);
if(info == null){
info = new SQLCacheInfo();
}
info.put(map);
}
/**
* 返回id位置信息
*
* @param key
* @param index
* @return
*/
public static Entry<Integer, Object> getFloorEntry(String key,Integer index){
SQLCacheInfo info = (SQLCacheInfo)lmap.get(key);
if(info == null){
return null;
}
return info.getEntry(index);
}
public static Entry<Integer, Object> getCeilEntry(String key,Integer index){
SQLCacheInfo info = (SQLCacheInfo)lmap.get(key);
if(info == null){
return null;
}
return info.getCeilEntry(index);
}
/**
* 返回缓存id位置的数量
* @param key
* @return
*/
public static int getIndexSize(String key){
SQLCacheInfo info = (SQLCacheInfo)lmap.get(key);
if(info == null){
return -1;
}
return info.getIndexSize();
}
public static boolean isInitIndex(String key){
SQLCacheInfo info = (SQLCacheInfo)lmap.get(key);
if(info == null){
throw new RuntimeException(key+"没有找到");
}
return info.isInitIndex();
}
}
缓存信息SQLCacheInfo
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;
public class SQLCacheInfo {
/**
* 第多少条,id值多少
*/
private TreeMap<Integer,Object> IndexId = new TreeMap<Integer,Object>();
public static final int MOD = 50;
public static final int CAPTION = 10000;
private int totalSize;
public int getTotalSize() {
return totalSize;
}
public void setTotalSize(int totalSize) {
this.totalSize = totalSize;
}
/**
* 根据条数返回id值
* @param index
* @return
*/
public Entry<Integer,Object> getEntry(int index){
return IndexId.floorEntry(index);
}
public Entry<Integer,Object> getCeilEntry(int index){
return IndexId.ceilingEntry(index);
}
public void put(Map<Integer,Object> map){
IndexId.putAll(map);
}
public int getIndexSize(){
if(totalSize < 10000)
return -1;
return Math.max(CAPTION,(totalSize / 50));
}
public static void main(String[] args) {
SQLCacheInfo info = new SQLCacheInfo();
HashMap map = new HashMap();
map.put(10, 2);
map.put(40, 2);
map.put(60, 2);
map.put(80, 2);
info.put(map);
System.out.println(info.getEntry(100).getKey());
}
public boolean isInitIndex(){
return this.IndexId.size()>0;
}
}