The important thing in life is to have a great aim , and the determination

常用链接

统计

IT技术链接

保险相关

友情链接

基金知识

生活相关

最新评论

2007年5月18日 #

POI操作Excel

一、POI概述   Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。   结构: HSSF - 提供读写Microsoft Excel格式档案的功能。 XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。 HWPF - 提供读写Microsoft Word格式档案的功能。 HSLF - 提供读写Microsoft PowerPoint格式档案的功能。 HDGF - 提供读写Microsoft Visio格式档案的功能。  使用必须引入依赖 org.apache.poi poi 3.17 注:3.17版本是支持jdk6的最后版本 二、HSSF概况   HSSF 是Horrible SpreadSheet Format的缩写,通过HSSF,你可以用纯Java代码来读取、写入、修改Excel文件。HSSF 为读取操作提供了两类API:usermodel和eventusermodel,即“用户模型”和“事件-用户模型”。 三、 POI EXCEL文档结构类 HSSFWorkbook excel文档对象 HSSFSheet excel的sheet HSSFRow excel的行 HSSFCell excel的单元格 HSSFFont excel字体 HSSFName 名称 HSSFDataFormat 日期格式 HSSFHeader sheet头 HSSFFooter sheet尾 HSSFCellStyle cell样式 HSSFDateUtil 日期 HSSFPrintSetup 打印 HSSFErrorConstants 错误信息表 四、EXCEL的读写操作 1、读取“区域数据.xls”并储存于list集合中,“区域数据.xls”如下图 public List importXLS(){ ArrayList list = new ArrayList<>(); try {      //1、获取文件输入流      InputStream inputStream = new FileInputStream("/Users/Shared/区域数据.xls");      //2、获取Excel工作簿对象 HSSFWorkbook workbook = new HSSFWorkbook(inputStream);      //3、得到Excel工作表对象 HSSFSheet sheetAt = workbook.getSheetAt(0); //4、循环读取表格数据      for (Row row : sheetAt) {        //首行(即表头)不读取 if (row.getRowNum() == 0) { continue; } //读取当前行中单元格数据,索引从0开始        String areaNum = row.getCell(0).getStringCellValue(); String province = row.getCell(1).getStringCellValue(); String city = row.getCell(2).getStringCellValue(); String district = row.getCell(3).getStringCellValue(); String postcode = row.getCell(4).getStringCellValue(); Area area = new Area(); area.setCity(city); area.setDistrict(district); area.setProvince(province);        area.setPostCode(postcode); list.add(area); }      //5、关闭流 workbook.close(); } catch (IOException e) { e.printStackTrace(); }   return list; } 2、导出数据到“区域数据.xls”文件中,页面数据如下图: public void exportExcel() throws IOException { Page page = areaService.pageQuery(null); List list = page.getContent(); //1.在内存中创建一个excel文件 HSSFWorkbook hssfWorkbook = new HSSFWorkbook(); //2.创建工作簿 HSSFSheet sheet = hssfWorkbook.createSheet(); //3.创建标题行 HSSFRow titlerRow = sheet.createRow(0); titlerRow.createCell(0).setCellValue("省"); titlerRow.createCell(1).setCellValue("市"); titlerRow.createCell(2).setCellValue("区"); titlerRow.createCell(3).setCellValue("邮编"); titlerRow.createCell(4).setCellValue("简码"); titlerRow.createCell(5).setCellValue("城市编码"); //4.遍历数据,创建数据行 for (Area area : list) { //获取最后一行的行号 int lastRowNum = sheet.getLastRowNum(); HSSFRow dataRow = sheet.createRow(lastRowNum + 1); dataRow.createCell(0).setCellValue(area.getProvince()); dataRow.createCell(1).setCellValue(area.getCity()); dataRow.createCell(2).setCellValue(area.getDistrict()); dataRow.createCell(3).setCellValue(area.getPostcode()); dataRow.createCell(4).setCellValue(area.getShortcode()); dataRow.createCell(5).setCellValue(area.getCitycode()); } //5.创建文件名 String fileName = "区域数据统计.xls"; //6.获取输出流对象 HttpServletResponse response = ServletActionContext.getResponse(); ServletOutputStream outputStream = response.getOutputStream(); //7.获取mimeType ServletContext servletContext = ServletActionContext.getServletContext(); String mimeType = servletContext.getMimeType(fileName); //8.获取浏览器信息,对文件名进行重新编码 HttpServletRequest request = ServletActionContext.getRequest(); fileName = FileUtils.filenameEncoding(fileName, request); //9.设置信息头 response.setContentType(mimeType); response.setHeader("Content-Disposition","attachment;filename="+fileName); //10.写出文件,关闭流 hssfWorkbook.write(outputStream); hssfWorkbook.close(); } 工具类 public class FileUtils { public static String filenameEncoding(String filename, HttpServletRequest request) throws IOException { String agent = request.getHeader("User-Agent"); //获取浏览器 if (agent.contains("Firefox")) { BASE64Encoder base64Encoder = new BASE64Encoder(); filename = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?="; } else if(agent.contains("MSIE")) { filename = URLEncoder.encode(filename, "utf-8"); } else if(agent.contains ("Safari")) { filename = new String (filename.getBytes ("utf-8"),"ISO8859-1"); } else { filename = URLEncoder.encode(filename, "utf-8"); } return filename; } } 写出xls文件: 五、 EXCEL常用操作方法 1、 得到Excel常用对象 POIFSFileSystem fs=newPOIFSFileSystem(new FileInputStream("d:/test.xls")); //得到Excel工作簿对象 HSSFWorkbook wb = new HSSFWorkbook(fs); //得到Excel工作表对象 HSSFSheet sheet = wb.getSheetAt(0); //得到Excel工作表的行 HSSFRow row = sheet.getRow(i); //得到Excel工作表指定行的单元格 HSSFCell cell = row.getCell((short) j); cellStyle = cell.getCellStyle();//得到单元格样式 2、建立Excel常用对象 HSSFWorkbook wb = new HSSFWorkbook();//创建Excel工作簿对象 HSSFSheet sheet = wb.createSheet("new sheet");//创建Excel工作表对象 HSSFRow row = sheet.createRow((short)0); //创建Excel工作表的行 cellStyle = wb.createCellStyle();//创建单元格样式 row.createCell((short)0).setCellStyle(cellStyle); //创建Excel工作表指定行的单元格 row.createCell((short)0).setCellValue(1); //设置Excel工作表的值 3、设置sheet名称和单元格内容 wb.setSheetName(1, "第一张工作表",HSSFCell.ENCODING_UTF_16); cell.setEncoding((short) 1); cell.setCellValue("单元格内容"); 4、取得sheet的数目 wb.getNumberOfSheets() 5、 根据index取得sheet对象 HSSFSheet sheet = wb.getSheetAt(0); 6、取得有效的行数 int rowcount = sheet.getLastRowNum(); 7、取得一行的有效单元格个数 row.getLastCellNum(); 8、单元格值类型读写 cell.setCellType(HSSFCell.CELL_TYPE_STRING); //设置单元格为STRING类型 cell.getNumericCellValue();//读取为数值类型的单元格内容 9、设置列宽、行高 sheet.setColumnWidth((short)column,(short)width); row.setHeight((short)height); 10、添加区域,合并单元格 Region region = new Region((short)rowFrom,(short)columnFrom,(short)rowTo ,(short)columnTo);//合并从第rowFrom行columnFrom列 sheet.addMergedRegion(region);// 到rowTo行columnTo的区域 //得到所有区域 sheet.getNumMergedRegions() 11、保存Excel文件 FileOutputStream fileOut = new FileOutputStream(path); wb.write(fileOut); 12、根据单元格不同属性返回字符串数值 public String getCellStringValue(HSSFCell cell) {   String cellValue = "";   switch (cell.getCellType()) {     case HSSFCell.CELL_TYPE_STRING://字符串类型         cellValue = cell.getStringCellValue();         if(cellValue.trim().equals("")||cellValue.trim().length()<=0)           cellValue=" ";         break;     case HSSFCell.CELL_TYPE_NUMERIC: //数值类型         cellValue = String.valueOf(cell.getNumericCellValue());         break;     case HSSFCell.CELL_TYPE_FORMULA: //公式         cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC);         cellValue = String.valueOf(cell.getNumericCellValue());         break;     case HSSFCell.CELL_TYPE_BLANK:         cellValue=" ";         break;     case HSSFCell.CELL_TYPE_BOOLEAN:         break;     case HSSFCell.CELL_TYPE_ERROR:         break;     default:         break;   }   return cellValue; } 13、常用单元格边框格式 HSSFCellStyle style = wb.createCellStyle(); style.setBorderBottom(HSSFCellStyle.BORDER_DOTTED);//下边框 style.setBorderLeft(HSSFCellStyle.BORDER_DOTTED);//左边框 style.setBorderRight(HSSFCellStyle.BORDER_THIN);//右边框 style.setBorderTop(HSSFCellStyle.BORDER_THIN);//上边框 14、设置字体和内容位置 HSSFFont f = wb.createFont(); f.setFontHeightInPoints((short) 11);//字号 f.setBoldweight(HSSFFont.BOLDWEIGHT_NORMAL);//加粗 style.setFont(f); style.setAlignment(HSSFCellStyle.ALIGN_CENTER);//左右居中 style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//上下居中 style.setRotation(short rotation);//单元格内容的旋转的角度 HSSFDataFormat df = wb.createDataFormat(); style1.setDataFormat(df.getFormat("0.00%"));//设置单元格数据格式 cell.setCellFormula(string);//给单元格设公式 style.setRotation(short rotation);//单元格内容的旋转的角度 15、插入图片 //先把读进来的图片放到一个ByteArrayOutputStream中,以便产生ByteArray ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream(); BufferedImage bufferImg = ImageIO.read(new File("ok.jpg")); ImageIO.write(bufferImg,"jpg",byteArrayOut); //读进一个excel模版 FileInputStream fos = new FileInputStream(filePathName+"/stencil.xlt"); fs = new POIFSFileSystem(fos); //创建一个工作薄 HSSFWorkbook wb = new HSSFWorkbook(fs); HSSFSheet sheet = wb.getSheetAt(0); HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); HSSFClientAnchor anchor = new HSSFClientAnchor(0,0,1023,255,(short) 0,0,(short)10,10); patriarch.createPicture(anchor , wb.addPicture(byteArrayOut.toByteArray(),HSSFWorkbook.PICTURE_TYPE_JPEG)); 16、调整工作表位置 HSSFWorkbook wb = new HSSFWorkbook(); HSSFSheet sheet = wb.createSheet("format sheet"); HSSFPrintSetup ps = sheet.getPrintSetup(); sheet.setAutobreaks(true); ps.setFitHeight((short)1); ps.setFitWidth((short)1);

posted @ 2020-03-20 10:13 鸿雁 阅读(94) | 评论 (0)编辑 收藏

python操作文件存在的问题解决办法

1、在学习从文件读取数据中,写了个示例代码,读取不在同一个目录的file.txt,运行后报这个Python OSError: [Errno 22] Invalid argument:错误: (1)、首先,在F盘的python_stu中新增了一个file.txt,同时在F盘的python_stu文件目录底下新增一个file文件夹,里面有个file_reader.py来读取python_stu文件目录底下的file.txt,代码分别如下: file.txt: 测试 测试2 测试3 file_reader.py: with open('F:\python_stu\file.txt') as file_obj: contents = file_obj.read(); print(contents.rstrip()); (2)、运行后报错: (3)、出现这种错误的原因是由于读取不到这个文件,看Traceback报的错误,最后一行,很明显读取不到file.txt,前面的F:\\python_stu没错,后面的名称怎么变了,还是x0cile.txt。 (4)、解决办法,可修改上述第一行代码为: with open('F:\python_stu/file.txt') as file_obj: 或者: with open('F:/python_stu/file.txt') as file_obj: 或者: with open('F://python_stu//file.txt') as file_obj: 又或者: with open('F:\\python_stu\\file.txt') as file_obj: 还有一些我就不附上了,上面第一种方式不统一,最好不要用,用统一的方式,而且有时候还有注意一些转义字符,比如 \t,\n也会导致报错。

posted @ 2019-05-14 23:04 鸿雁 阅读(143) | 评论 (0)编辑 收藏

Hadoop-2.4.1学习之QJM HA的自动故障转移

前面学习了使用命令hdfs haadmin -failover手动进行故障转移,在该模式下,即使现役NameNode已经失效,系统也不会自动从现役NameNode转移到待机NameNode,下面学习如何配置部署HA自动进行故障转移。自动故障转移为HDFS部署增加了两个新组件:ZooKeeper和ZKFailoverController(ZKFC)进程。ZooKeeper是维护少量协调数据,通知客户端这些数据的改变和监视客户端故障的高可用服务。HA的自动故障转移依赖于ZooKeeper的以下功能:

  • 故障检测:集群中的每个NameNode在ZooKeeper中维护了一个持久会话,如果机器崩溃,ZooKeeper中的会话将终止,ZooKeeper通知另一个NameNode需要触发故障转移。
  • 现役NameNode选择:ZooKeeper提供了一个简单的机制用于唯一的选择一个节点为active状态。如果目前现役NameNode崩溃,另一个节点可能从ZooKeeper获得特殊的排外锁以表明它应该成为现役NameNode。

       ZKFC是自动故障转移中的另一个新组件,是ZooKeeper的客户端,也监视和管理NameNode的状态。每个运行NameNode的主机也运行了一个ZKFC进程,ZKFC负责:

  • 健康监测:ZKFC使用一个健康检查命令定期地ping与之在相同主机的NameNode,只要该NameNode及时地回复健康状态,ZKFC认为该节点是健康的。如果该节点崩溃,冻结或进入不健康状态,健康监测器标识该节点为非健康的。
  • ZooKeeper会话管理:当本地NameNode是健康的,ZKFC保持一个在ZooKeeper中打开的会话。如果本地NameNode处于active状态,ZKFC也保持一个特殊的znode锁,该锁使用了ZooKeeper对短暂节点的支持,如果会话终止,锁节点将自动删除。
  • 基于ZooKeeper的选择:如果本地NameNode是健康的,且ZKFC发现没有其它的节点当前持有znode锁,它将为自己获取该锁。如果成功,则它已经赢得了选择,并负责运行故障转移进程以使它的本地NameNode为active。故障转移进城与前面描述的手动故障转移相似,首先如果必要保护之前的现役NameNode,然后本地NameNode转换为active状态。

       在典型部署中,ZooKeeper守护进程运行在三个或者五个节点上,但由于ZooKeeper本身需要较少的资源,所以将ZooKeeper部署在与现役NameNode和待机NameNode相同的主机上,还可以将ZooKeeper部署到与YARN的ResourceManager相同的节点上。建议配置ZooKeeper将数据存储在与HDFS元数据不同的硬盘上以得到最好的性能和隔离性。在配置自动故障转移之前需要先停掉集群,目前在集群运行时还不可能将手动故障转移的安装转换为自动故障转移的安装。接下来看看如何配置HA的自动故障转移。首先在hdfs-site.xml中添加下面的参数,该参数的值默认为false:

  1. <property>  
  2.    <name>dfs.ha.automatic-failover.enabled</name>  
  3.    <value>true</value>  
  4. </property>  

       在core-site.xml文件中添加下面的参数,该参数的值为ZooKeeper服务器的地址,ZKFC将使用该地址。

  1. <property>  
  2.    <name>ha.zookeeper.quorum</name> <value>zk1.example.com:2181,zk2.example.com:2181,zk3.example.com:2181</value>  
  3. </property>  

       在HA或者HDFS联盟中,上面的两个参数还需要以NameServiceID为后缀,比如dfs.ha.automatic-failover.enabled.mycluster。除了上面的两个参数外,还有其它几个参数用于自动故障转移,比如ha.zookeeper.session-timeout.ms,但对于大多数安装来说都不是必须的。

       在添加了上述的配置参数后,下一步就是在ZooKeeper中初始化要求的状态,可以在任一NameNode中运行下面的命令实现该目的,该命令将在ZooKeeper中创建znode:

  1. $ hdfs zkfc -formatZK  

       在启用自动故障转移的集群中,start-dfs.sh脚本将在任何运行NameNode的主机上自动启动ZKFC守护进程,一旦ZKFC启动完毕,它们将自动选择一个NameNode为现役NameNode。如果手动管理集群中的服务,需要在每台运行NameNode的主机上手动启动ZKFC,命令为:

  1. hadoop-daemon.sh start zkfc  
  2. hdfs zkfc  

       如果正在运行一个安全的集群,可能想确保存储在ZooKeeper中的信息也是安全的,这将阻止恶意的客户端修改ZooKeeper中的元数据或者潜在地触发一个错误的故障转移。为了保护ZooKeeper中的信息,首先在core-site.xml中添加下面的参数:

  1. <property>  
  2.    <name>ha.zookeeper.auth</name>  
  3.    <value>@/path/to/zk-auth.txt</value>  
  4.  </property>  
  5.  <property>  
  6.    <name>ha.zookeeper.acl</name>  
  7.    <value>@/path/to/zk-acl.txt</value>  
  8.  </property>  

       参数值中的@字符表示参数值保存在@后的硬盘文件中。第一个配置文件指定了ZooKeeper的认证列表,其格式与ZK CLI使用的相同,例如:digest:hdfs-zkfcs:mypassword,其中hdfs-zkfcs为ZooKeeper的用户名,mypassword为密码。其次使用下面的命令为该认证生成一个ZooKeeper访问控制列表:

  1. $ java -cp $ZK_HOME/lib/*:$ZK_HOME/zookeeper-3.4.2.jar org.apache.zookeeper.server.auth.DigestAuthenticationProvider hdfs-zkfcs:mypassword  
  2. output: hdfs-zkfcs:mypassword->hdfs-zkfcs:P/OQvnYyU/nF/mGYvB/xurX8dYs=   

       拷贝->之后的字符串并添加digest:前缀,然后粘贴到zk-acls.txt中,例如:digest:hdfs-zkfcs:vlUvLnd8MlacsE80rDuu6ONESbM=:rwcda。要想使ACLs生效,需要再次运行zkfc –formatZK。最后可能像下面这样在ZK CLI中验证ACLs:

  1. [zk: localhost:2181(CONNECTED) 1] getAcl /hadoop-ha  
  2. 'digest,'hdfs-zkfcs:vlUvLnd8MlacsE80rDuu6ONESbM=  
  3. : cdrwa  

       在安装完成自动故障转移后,或许需要测试一下。首先定位现役NameNode,可以通过访问NameNode的web页面来确定哪个NameNode是active状态的。一旦确定了处于active状态的NameNode,就需要在该节点上制造点故障,比如使用命令kill -9 <pid of NN>模拟JVM崩溃,或重启主机或拔掉网线来模拟不同的中断。一旦触发了自动故障转移,另一个NameNode应该自动在几秒钟内变为active状态。检测到故障并触发故障转移由参数ha.zookeeper.session-timeout.ms控制,该参数为与core-site.xml中,默认为5秒。如果测试不成功,可能是配置问题,检查ZKFC和NameNode进程的日志以进一步诊断问题,通常错误都是很明显的。

posted @ 2017-08-13 18:49 鸿雁 阅读(409) | 评论 (0)编辑 收藏

Yarn 调度器Scheduler详解

理想情况下,我们应用对Yarn资源的请求应该立刻得到满足,但现实情况资源往往是有限的,特别是在一个很繁忙的集群,一个应用资源的请求经常需要等待一段时间才能的到相应的资源。在Yarn中,负责给应用分配资源的就是Scheduler。其实调度本身就是一个难题,很难找到一个完美的策略可以解决所有的应用场景。为此,Yarn提供了多种调度器和可配置的策略供我们选择。

一、调度器的选择

在Yarn中有三种调度器可以选择:FIFO Scheduler ,Capacity SchedulerFairS cheduler

FIFO Scheduler把应用按提交的顺序排成一个队列,这是一个先进先出队列,在进行资源分配的时候,先给队列中最头上的应用进行分配资源,待最头上的应用需求满足后再给下一个分配,以此类推。

FIFO Scheduler是最简单也是最容易理解的调度器,也不需要任何配置,但它并不适用于共享集群。大的应用可能会占用所有集群资源,这就导致其它应用被阻塞。在共享集群中,更适合采用Capacity SchedulerFair Scheduler,这两个调度器都允许大任务和小任务在提交的同时获得一定的系统资源。

下面“Yarn调度器对比图”展示了这几个调度器的区别,从图中可以看出,在FIFO 调度器中,小任务会被大任务阻塞。

而对于Capacity调度器,有一个专门的队列用来运行小任务,但是为小任务专门设置一个队列会预先占用一定的集群资源,这就导致大任务的执行时间会落后于使用FIFO调度器时的时间。

在Fair调度器中,我们不需要预先占用一定的系统资源,Fair调度器会为所有运行的job动态的调整系统资源。如下图所示,当第一个大job提交时,只有这一个job在运行,此时它获得了所有集群资源;当第二个小任务提交后,Fair调度器会分配一半资源给这个小任务,让这两个任务公平的共享集群资源。

需要注意的是,在下图Fair调度器中,从第二个任务提交到获得资源会有一定的延迟,因为它需要等待第一个任务释放占用的Container。小任务执行完成之后也会释放自己占用的资源,大任务又获得了全部的系统资源。最终的效果就是Fair调度器即得到了高的资源利用率又能保证小任务及时完成。

Yarn调度器对比图: 
技术分享

二、Capacity Scheduler(容器调度器)的配置

2.1 容器调度介绍

Capacity 调度器允许多个组织共享整个集群,每个组织可以获得集群的一部分计算能力。通过为每个组织分配专门的队列,然后再为每个队列分配一定的集群资源,这样整个集群就可以通过设置多个队列的方式给多个组织提供服务了。除此之外,队列内部又可以垂直划分,这样一个组织内部的多个成员就可以共享这个队列资源了,在一个队列内部,资源的调度是采用的是先进先出(FIFO)策略。

通过上面那幅图,我们已经知道一个job可能使用不了整个队列的资源。然而如果这个队列中运行多个job,如果这个队列的资源够用,那么就分配给这些job,如果这个队列的资源不够用了呢?其实Capacity调度器仍可能分配额外的资源给这个队列,这就是“弹性队列”(queue elasticity)的概念。

在正常的操作中,Capacity调度器不会强制释放Container,当一个队列资源不够用时,这个队列只能获得其它队列释放后的Container资源。当然,我们可以为队列设置一个最大资源使用量,以免这个队列过多的占用空闲资源,导致其它队列无法使用这些空闲资源,这就是”弹性队列”需要权衡的地方。

2.2 容器调度的配置

假设我们有如下层次的队列:

root ├── prod └── dev     ├── eng     └── science 

下面是一个简单的Capacity调度器的配置文件,文件名为capacity-scheduler.xml。在这个配置中,在root队列下面定义了两个子队列proddev,分别占40%和60%的容量。需要注意,一个队列的配置是通过属性yarn.sheduler.capacity.<queue-path>.<sub-property>指定的,<queue-path>代表的是队列的继承树,如root.prod队列,<sub-property>一般指capacitymaximum-capacity

技术分享

我们可以看到,dev队列又被分成了engscience两个相同容量的子队列。devmaximum-capacity属性被设置成了75%,所以即使prod队列完全空闲dev也不会占用全部集群资源,也就是说,prod队列仍有25%的可用资源用来应急。我们注意到,engscience两个队列没有设置maximum-capacity属性,也就是说engscience队列中的job可能会用到整个dev队列的所有资源(最多为集群的75%)。而类似的,prod由于没有设置maximum-capacity属性,它有可能会占用集群全部资源。

Capacity容器除了可以配置队列及其容量外,我们还可以配置一个用户或应用可以分配的最大资源数量、可以同时运行多少应用、队列的ACL认证等。

2.3 队列的设置

关于队列的设置,这取决于我们具体的应用。比如,在MapReduce中,我们可以通过mapreduce.job.queuename属性指定要用的队列。如果队列不存在,我们在提交任务时就会收到错误。如果我们没有定义任何队列,所有的应用将会放在一个default队列中。

注意:对于Capacity调度器,我们的队列名必须是队列树中的最后一部分,如果我们使用队列树则不会被识别。比如,在上面配置中,我们使用prodeng作为队列名是可以的,但是如果我们用root.dev.eng或者dev.eng是无效的。

三、Fair Scheduler(公平调度器)的配置

3.1 公平调度

Fair调度器的设计目标是为所有的应用分配公平的资源(对公平的定义可以通过参数来设置)。在上面的“Yarn调度器对比图”展示了一个队列中两个应用的公平调度;当然,公平调度在也可以在多个队列间工作。举个例子,假设有两个用户A和B,他们分别拥有一个队列。当A启动一个job而B没有任务时,A会获得全部集群资源;当B启动一个job后,A的job会继续运行,不过一会儿之后两个任务会各自获得一半的集群资源。如果此时B再启动第二个job并且其它job还在运行,则它将会和B的第一个job共享B这个队列的资源,也就是B的两个job会用于四分之一的集群资源,而A的job仍然用于集群一半的资源,结果就是资源最终在两个用户之间平等的共享。过程如下图所示: 
技术分享

3.2 启用Fair Scheduler

调度器的使用是通过yarn-site.xml配置文件中的yarn.resourcemanager.scheduler.class参数进行配置的,默认采用Capacity Scheduler调度器。如果我们要使用Fair调度器,需要在这个参数上配置FairScheduler类的全限定名: org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler

3.3 队列的配置

Fair调度器的配置文件位于类路径下的fair-scheduler.xml文件中,这个路径可以通过yarn.scheduler.fair.allocation.file属性进行修改。若没有这个配置文件,Fair调度器采用的分配策略,这个策略和3.1节介绍的类似:调度器会在用户提交第一个应用时为其自动创建一个队列,队列的名字就是用户名,所有的应用都会被分配到相应的用户队列中。

我们可以在配置文件中配置每一个队列,并且可以像Capacity 调度器一样分层次配置队列。比如,参考capacity-scheduler.xml来配置fair-scheduler: 
技术分享

队列的层次是通过嵌套<queue>元素实现的。所有的队列都是root队列的孩子,即使我们没有配到<root>元素里。在这个配置中,我们把dev队列有分成了engscience两个队列。

Fair调度器中的队列有一个权重属性(这个权重就是对公平的定义),并把这个属性作为公平调度的依据。在这个例子中,当调度器分配集群40:60资源给proddev时便视作公平,engscience队列没有定义权重,则会被平均分配。这里的权重并不是百分比,我们把上面的40和60分别替换成2和3,效果也是一样的。注意,对于在没有配置文件时按用户自动创建的队列,它们仍有权重并且权重值为1。

每个队列内部仍可以有不同的调度策略。队列的默认调度策略可以通过顶级元素<defaultQueueSchedulingPolicy>进行配置,如果没有配置,默认采用公平调度。

尽管是Fair调度器,其仍支持在队列级别进行FIFO调度。每个队列的调度策略可以被其内部的<schedulingPolicy> 元素覆盖,在上面这个例子中,prod队列就被指定采用FIFO进行调度,所以,对于提交到prod队列的任务就可以按照FIFO规则顺序的执行了。需要注意,proddev之间的调度仍然是公平调度,同样engscience也是公平调度。

尽管上面的配置中没有展示,每个队列仍可配置最大、最小资源占用数和最大可运行的应用的数量。

3.4 队列的设置

Fair调度器采用了一套基于规则的系统来确定应用应该放到哪个队列。在上面的例子中,<queuePlacementPolicy> 元素定义了一个规则列表,其中的每个规则会被逐个尝试直到匹配成功。例如,上例第一个规则specified,则会把应用放到它指定的队列中,若这个应用没有指定队列名或队列名不存在,则说明不匹配这个规则,然后尝试下一个规则。primaryGroup规则会尝试把应用放在以用户所在的Unix组名命名的队列中,如果没有这个队列,不创建队列转而尝试下一个规则。当前面所有规则不满足时,则触发default规则,把应用放在dev.eng队列中。

当然,我们可以不配置queuePlacementPolicy规则,调度器则默认采用如下规则:

<queuePlacementPolicy> <rule name="specified" /> <rule name="user" /> </queuePlacementPolicy> 

上面规则可以归结成一句话,除非队列被准确的定义,否则会以用户名为队列名创建队列。

还有一个简单的配置策略可以使得所有的应用放入同一个队列(default),这样就可以让所有应用之间平等共享集群而不是在用户之间。这个配置的定义如下:

<queuePlacementPolicy> <rule name="default" /> </queuePlacementPolicy> 

实现上面功能我们还可以不使用配置文件,直接设置yarn.scheduler.fair.user-as-default-queue=false,这样应用便会被放入default 队列,而不是各个用户名队列。另外,我们还可以设置yarn.scheduler.fair.allow-undeclared-pools=false,这样用户就无法创建队列了。

3.5 抢占(Preemption)

当一个job提交到一个繁忙集群中的空队列时,job并不会马上执行,而是阻塞直到正在运行的job释放系统资源。为了使提交job的执行时间更具预测性(可以设置等待的超时时间),Fair调度器支持抢占。

抢占就是允许调度器杀掉占用超过其应占份额资源队列的containers,这些containers资源便可被分配到应该享有这些份额资源的队列中。需要注意抢占会降低集群的执行效率,因为被终止的containers需要被重新执行。

可以通过设置一个全局的参数yarn.scheduler.fair.preemption=true来启用抢占功能。此外,还有两个参数用来控制抢占的过期时间(这两个参数默认没有配置,需要至少配置一个来允许抢占Container):

- minimum share preemption timeout - fair share preemption timeout 

如果队列在minimum share preemption timeout指定的时间内未获得最小的资源保障,调度器就会抢占containers。我们可以通过配置文件中的顶级元素<defaultMinSharePreemptionTimeout>为所有队列配置这个超时时间;我们还可以在<queue>元素内配置<minSharePreemptionTimeout>元素来为某个队列指定超时时间。

与之类似,如果队列在fair share preemption timeout指定时间内未获得平等的资源的一半(这个比例可以配置),调度器则会进行抢占containers。这个超时时间可以通过顶级元素<defaultFairSharePreemptionTimeout>和元素级元素<fairSharePreemptionTimeout>分别配置所有队列和某个队列的超时时间。上面提到的比例可以通过<defaultFairSharePreemptionThreshold>(配置所有队列)和<fairSharePreemptionThreshold>(配置某个队列)进行配置,默认是0.5。

posted @ 2017-08-05 00:35 鸿雁 阅读(339) | 评论 (0)编辑 收藏

hive大数据倾斜总结

在做Shuffle阶段的优化过程中,遇到了数据倾斜的问题,造成了对一些情况下优化效果不明显。主要是因为在Job完成后的所得到的Counters是整个Job的总和,优化是基于这些Counters得出的平均值,而由于数据倾斜的原因造成map处理数据量的差异过大,使得这些平均值能代表的价值降低。Hive的执行是分阶段的,map处理数据量的差异取决于上一个stage的reduce输出,所以如何将数据均匀的分配到各个reduce中,就是解决数据倾斜的根本所在。规避错误来更好的运行比解决错误更高效。在查看了一些资料后,总结如下。

1数据倾斜的原因

1.1操作:

关键词

情形

后果

Join

其中一个表较小,

但是key集中

分发到某一个或几个Reduce上的数据远高于平均值

大表与大表,但是分桶的判断字段0值或空值过多

这些空值都由一个reduce处理,灰常慢

group by

group by 维度过小,

某值的数量过多

处理某值的reduce灰常耗时

Count Distinct

某特殊值过多

处理此特殊值的reduce耗时

1.2原因:

1)、key分布不均匀

2)、业务数据本身的特性

3)、建表时考虑不周

4)、某些SQL语句本身就有数据倾斜

 

1.3表现:

任务进度长时间维持在99%(或100%),查看任务监控页面,发现只有少量(1个或几个)reduce子任务未完成。因为其处理的数据量和其他reduce差异过大。

单一reduce的记录数与平均记录数差异过大,通常可能达到3倍甚至更多。 最长时长远大于平均时长。

 

2数据倾斜的解决方案

2.1参数调节:

hive.map.aggr=true

Map 端部分聚合,相当于Combiner

hive.groupby.skewindata=true

有数据倾斜的时候进行负载均衡,当选项设定为 true,生成的查询计划会有两个 MR Job。第一个 MR Job 中,Map 的输出结果集合会随机分布到 Reduce 中,每个 Reduce 做部分聚合操作,并输出结果,这样处理的结果是相同的 Group By Key 有可能被分发到不同的 Reduce 中,从而达到负载均衡的目的;第二个 MR Job 再根据预处理的数据结果按照 Group By Key 分布到 Reduce 中(这个过程可以保证相同的 Group By Key 被分布到同一个 Reduce 中),最后完成最终的聚合操作。

 

2.2 SQL语句调节:

如何Join

关于驱动表的选取,选用join key分布最均匀的表作为驱动表

做好列裁剪和filter操作,以达到两表做join的时候,数据量相对变小的效果。

大小表Join

使用map join让小的维度表(1000条以下的记录条数) 先进内存。在map端完成reduce.

大表Join大表:

把空值的key变成一个字符串加上随机数,把倾斜的数据分到不同的reduce上,由于null值关联不上,处理后并不影响最终结果。

count distinct大量相同特殊值

count distinct时,将值为空的情况单独处理,如果是计算count distinct,可以不用处理,直接过滤,在最后结果中加1。如果还有其他计算,需要进行group by,可以先将值为空的记录单独处理,再和其他计算结果进行union。

group by维度过小:

采用sum() group by的方式来替换count(distinct)完成计算。

特殊情况特殊处理:

在业务逻辑优化效果的不大情况下,有些时候是可以将倾斜的数据单独拿出来处理。最后union回去。

 

3典型的业务场景

3.1空值产生的数据倾斜

场景:如日志中,常会有信息丢失的问题,比如日志中的 user_id,如果取其中的 user_id 和 用户表中的user_id 关联,会碰到数据倾斜的问题。

解决方法1 user_id为空的不参与关联(红色字体为修改后)

复制代码
select * from log a   join users b   on a.user_id is not null   and a.user_id = b.user_id union all select * from log a   where a.user_id is null;
复制代码

 

解决方法赋与空值分新的key值

select *   from log a   left outer join users b   on case when a.user_id is null then concat(‘hive’,rand() ) else a.user_id end = b.user_id;

 

结论:方法2比方法1效率更好,不但io少了,而且作业数也少了。解决方法1中 log读取两次,jobs是2。解决方法2 job数是1 。这个优化适合无效 id (比如 -99 , ’’, null 等) 产生的倾斜问题。把空值的 key 变成一个字符串加上随机数,就能把倾斜的数据分到不同的reduce上 ,解决数据倾斜问题。

 

3.2不同数据类型关联产生数据倾斜

场景:用户表中user_id字段为int,log表中user_id字段既有string类型也有int类型。当按照user_id进行两个表的Join操作时,默认的Hash操作会按int型的id来进行分配,这样会导致所有string类型id的记录都分配到一个Reducer中。

解决方法:把数字类型转换成字符串类型

select * from users a   left outer join logs b   on a.usr_id = cast(b.user_id as string)

 

3.3小表不小不大,怎么用 map join 解决倾斜问题

使用 map join 解决小表(记录数少)关联大表的数据倾斜问题,这个方法使用的频率非常高,但如果小表很大,大到map join会出现bug或异常,这时就需要特别的处理。 以下例子:

select * from log a   left outer join users b   on a.user_id = b.user_id;

 

users 表有 600w+ 的记录,把 users 分发到所有的 map 上也是个不小的开销,而且 map join 不支持这么大的小表。如果用普通的 join,又会碰到数据倾斜的问题。

解决方法:

select /*+mapjoin(x)*/* from log a   
left outer join (
select /*+mapjoin(c)*/d.* from (
select distinct user_id from log ) c join users d
on c.user_id = d.user_id ) x
on a.user_id = b.user_id;

假如,log里user_id有上百万个,这就又回到原来map join问题。所幸,每日的会员uv不会太多,有交易的会员不会太多,有点击的会员不会太多,有佣金的会员不会太多等等。所以这个方法能解决很多场景下的数据倾斜问题。

4总结

使map的输出数据更均匀的分布到reduce中去,是我们的最终目标。由于Hash算法的局限性,按key Hash会或多或少的造成数据倾斜。大量经验表明数据倾斜的原因是人为的建表疏忽或业务逻辑可以规避的。在此给出较为通用的步骤:

1、采样log表,哪些user_id比较倾斜,得到一个结果表tmp1。由于对计算框架来说,所有的数据过来,他都是不知道数据分布情况的,所以采样是并不可少的。

2、数据的分布符合社会学统计规则,贫富不均。倾斜的key不会太多,就像一个社会的富人不多,奇特的人不多一样。所以tmp1记录数会很少。把tmp1和users做map join生成tmp2,把tmp2读到distribute file cache。这是一个map过程。

3、map读入users和log,假如记录来自log,则检查user_id是否在tmp2里,如果是,输出到本地文件a,否则生成<user_id,value>的key,value对,假如记录来自member,生成<user_id,value>的key,value对,进入reduce阶段。

4、最终把a文件,把Stage3 reduce阶段输出的文件合并起写到hdfs。

 

如果确认业务需要这样倾斜的逻辑,考虑以下的优化方案:

1、对于join,在判断小表不大于1G的情况下,使用map join

2、对于group by或distinct,设定 hive.groupby.skewindata=true

3、尽量使用上述的SQL语句调节进行优化

posted @ 2017-07-31 19:57 鸿雁 阅读(163) | 评论 (0)编辑 收藏

Hive性能调优

Hive的一般学习者和培训者在谈性能优化的时候一般都会从语法和参数这些雕虫小技的角度谈优化,而不会革命性的优化Hive的性能,产生这种现象的原因有:
1,历史原因和思维定势:大家学习SQL的时候一般都是就单机DB,这个时候你的性能优化技巧确实主要是SQL语法和参数调优;
2,Hive的核心的性能问题往往是产生在超过规模数据集,例如说100亿条级别的数据集,以及每天处理上千上万个Hive作业的情况下产生的;

上面的第二点是我们现在Hive性能调优部分要彻底解决的内容;

要从根本上解决和显著的解决实际企业中Hive真正的性能优化问题,必须考虑到底什么是Hive性能的限制,我们按照优先级来说:
第一重要的是:战略性架构
 解决海量数据下大量Job过于频繁的IO问题,而这个问题实质上涉及了架构方面的分表 数据复用 以及分区表等调优的方式;
    补充:1,海量的数据中有些数据是高频使用的数据,而有些是很少使用的,如果能够分离成为不同的表,会极大的提升效率;很多的作业可能会有共同点,抽离出来先进行计算并保留计算结果,后面的作业都可以复用;同时,底层的基础功能
也可以先计算,在上层应用的时候直接拿数据结果,而不是每次都重复计算;
    2,合理从用静态分区表和动态分区表,可以避免数据全局扫描及计算资源更合理的利用;

    3,数据倾斜的一站式解决方案;
第二重要的是:引擎和物理层面,很多内容都是普通Hive使用这不知道的!
    从Hive语法和Job内部的角度去进行优化,这要求MapReduce以及Hive如何被翻译成为MapReduce要非常精通;

第三重要的是:一些关键的参数;



归根到底,Hive的性能优化主要考虑的是如何最大化和最有效的使用CPU Memory IO;



Hive背后的Mapper调优:
1,Mapper数过大,会产生大量小文件,由于Mapper是基于虚拟机的,过多的Mapper创建和初始化及关闭虚拟机都会消耗大量的硬件资源;
    Mapper数太小,并发度过小,Job执行时间过长,无法充分利用分布式硬件资源;
2,Mapper数据由什么决定呢?
    输入文件数目;
    输入文件的大小;
    配置参数;
 默认情况下:例如一个文件800M,BLock大小是128M,那么Mapper数目就是7个,6个Mapper处理的数据是 128M, 1个Mapper处理的数据是32M;再例如,一个目录下有三个文件分别大小问5M 10M 150M
 此时会产生4个Mapper,处理的数据分别是5M 10M 128M 22M;
  
 减少Mapper的个数,就要合并小文件,这种小文件有可能是直接来自于数据源的小文件,也可能是Reducer产生的小文件;
    set hive.input.format=org.apache.Hadoop.hive.ql.io.CombineHiveInputFormat;
    set hive.merge.mapFiles=true;
    set hive.merge.mapredFiles=true;
    set hive.merge.size.per.task=256000000
    set mapred.max.split.size=256000000
    set mapred.min.split.size.per.node=128000000
    
    
 增加Mapper的个数,一般是通过控制Hive SQL中上一个Job的Reducer个数来控制的,例如在Join操作的时候会把多个表分解为多个Job;


 set mapred.map.tasks=2;
 set hive.merge.mapFiles=true;
 set hive.merge.mapredFiles=true;
 set hive.merge.size.per.task=256000000

 例如我们有5个300M的文件;按照上面的配置会产生10个Mapper,5个Mapper处理的都是256M的数据,另外5个Mapper处理的都是44M的数据,问题是:大的Mapper会数据倾斜
如何解决,设置set mapred.map.tasks=6,此时根据MapRed的运行机制,会划分6个Mapper,每个Mapper的处理数据的大小是250M, min(1500M/6, 256M) =250M

Hive背后的Reducer调优:
1,Reducer数目过大的话,会产生很多小文件,每个Reducer都会产生一个文件,如果这些小文件是下一个JOB的输入,则会需要对小文件进行合并;同样启动 初始化和销毁Reducer的虚拟机也需要消耗大量的硬件;
  Reducer数据过小的话,Reduce的时间会比较长,也可能会出现数据倾斜;
2,如何控制Reducer的个数呢?
  set hive.exec.reducers.byte.per.reducer=1G
  set hive.exec.reducers.max=999
  Reducer个数=min(999, Reducer的数据输入总量/1G);

  set mapred.reduce.tasks = 10, 默认是1; 如果说当前的Reducer的结果很大,且被接下来多个Job使用其结果,我们该如何设置参数呢?一般都需要调大该参数;

  什么情况下只有一个Reducer?如果不进行Group by但却需要汇总,或者说Order by,当然如果最后Reducer的数据小于默认的1G的话,也会只有一个Reducer;


1,Hive在分布式运行的时候最害怕的是数据倾斜,这是由于分布式系统的特性决定的,因为分布式系统之所以很快是由于作业平均分配给了不同的节点,不同节点同心协力,从而达到更快处理完作业的目的;
顺便说明一下,处理数据倾斜的能力是hadoopSpark工程师最核心的竞争力之一;

2,Hive中数据倾斜的原因:
    数据在分布式节点上分布不平衡;
    join时某些key可能特别大;
    groupBy的时候某个Key可能特别多;
    count(distinct)有可能出现数据倾斜,因为其内部首先会进行groupBy操作;


3,join,我们希望join时候key是分散,如果一个key的数据量特别大,有可能会出现数据倾斜和OOM,一个核心点是:小表join大表,在reduce阶段左侧的小表会加载进内存,减少OOM的风险;
4,大表join大表的情况:数据倾斜,例如null值,解决办法一般是要打散null值,例如说使用随机数等,如果数据倾斜比较严重,采用这种方式可以提升至少一倍的速度;
5,mapJoin:小表join(超)大表的时候,可以采用mapJoin的方式把小表全部加载到Mapper端的内存中/*+MAPJOIN(table_name)*/;
6,小表join(超)大表的时候,是否会自动进行mapJoin,想进行mapJoin,需要设置:set hive.auto.convert.join=true,Hive在进行join的时候会判断左表的大小来决定是否进行mapJoin:
    set hive.mapjoin.smalltable.filesize=128000000;
    set hive.mapjoin.cache.numrows=100000;
    上述参数可以根据实际的硬件机器的内存进行调整,对性能有至关重要的影响,因为没有了Shuffle;
  对于mapJoin我们能够使用Mapper端JVM中多大的内存呢?
    set hive.mapjoin.followby.gby.localtask.max.momery.usage = 0.8
    set hive.mapjoin.localtask.max.memory.uage=0.9
7,groupBy,我们可以设置在Mapper端进行部分聚合,最后在Reducer端进行全局聚合
    set hive.map.aggr=true;
    set hive.groupby.mapaggr.checkinterval=100000

    set hive.groupby.skewindata = true 内部会产生两个Job,第一个Job会通过自己的算法打散倾斜的Key并进行聚合操作且保留结果,第二个Job会完成全部的groupBy操作,会产生Mapper-Reducer-Reducer的结构

8, count(distinct),如果某个字段特别多,容易产生数据倾斜,解决思路:
    在查询语句中例如对null进行过滤,在结果中加1


9, 笛卡尔积:join时候没有on条件,或者on条件无效,这个时候会使用Reducer进行笛卡尔积的操作;

posted @ 2017-07-30 13:52 鸿雁 阅读(175) | 评论 (0)编辑 收藏

HIVE和HBASE区别

1. 两者分别是什么?  

 Apache Hive是一个构建在Hadoop基础设施之上的数据仓库。通过Hive可以使用HQL语言查询存放在HDFS上的数据。HQL是一种类SQL语言,这种语言最终被转化为Map/Reduce. 虽然Hive提供了SQL查询功能,但是Hive不能够进行交互查询--因为它只能够在Haoop上批量的执行Hadoop。

    Apache HBase是一种Key/Value系统,它运行在HDFS之上。和Hive不一样,Hbase的能够在它的数据库上实时运行,而不是运行MapReduce任务。Hive被分区为表格,表格又被进一步分割为列簇。列簇必须使用schema定义,列簇将某一类型列集合起来(列不要求schema定义)。例如,“message”列簇可能包含:“to”, ”from” “date”, “subject”, 和”body”. 每一个 key/value对在Hbase中被定义为一个cell,每一个key由row-key,列簇、列和时间戳。在Hbase中,行是key/value映射的集合,这个映射通过row-key来唯一标识。Hbase利用Hadoop的基础设施,可以利用通用的设备进行水平的扩展。

2. 两者的特点

  Hive帮助熟悉SQL的人运行MapReduce任务。因为它是JDBC兼容的,同时,它也能够和现存的SQL工具整合在一起。运行Hive查询会花费很长时间,因为它会默认遍历表中所有的数据。虽然有这样的缺点,一次遍历的数据量可以通过Hive的分区机制来控制。分区允许在数据集上运行过滤查询,这些数据集存储在不同的文件夹内,查询的时候只遍历指定文件夹(分区)中的数据。这种机制可以用来,例如,只处理在某一个时间范围内的文件,只要这些文件名中包括了时间格式。

    HBase通过存储key/value来工作。它支持四种主要的操作:增加或者更新行,查看一个范围内的cell,获取指定的行,删除指定的行、列或者是列的版本。版本信息用来获取历史数据(每一行的历史数据可以被删除,然后通过Hbase compactions就可以释放出空间)。虽然HBase包括表格,但是schema仅仅被表格和列簇所要求,列不需要schema。Hbase的表格包括增加/计数功能。

3. 限制

  Hive目前不支持更新操作。另外,由于hive在hadoop上运行批量操作,它需要花费很长的时间,通常是几分钟到几个小时才可以获取到查询的结果。Hive必须提供预先定义好的schema将文件和目录映射到列,并且Hive与ACID不兼容。

    HBase查询是通过特定的语言来编写的,这种语言需要重新学习。类SQL的功能可以通过Apache Phonenix实现,但这是以必须提供schema为代价的。另外,Hbase也并不是兼容所有的ACID特性,虽然它支持某些特性。最后但不是最重要的--为了运行Hbase,Zookeeper是必须的,zookeeper是一个用来进行分布式协调的服务,这些服务包括配置服务,维护元信息和命名空间服务。

4. 应用场景

    Hive适合用来对一段时间内的数据进行分析查询,例如,用来计算趋势或者网站的日志。Hive不应该用来进行实时的查询。因为它需要很长时间才可以返回结果。

    Hbase非常适合用来进行大数据的实时查询。Facebook用Hbase进行消息和实时的分析。它也可以用来统计Facebook的连接数。

5. 总结

    Hive和Hbase是两种基于Hadoop的不同技术--Hive是一种类SQL的引擎,并且运行MapReduce任务,Hbase是一种在Hadoop之上的NoSQL 的Key/vale数据库。当然,这两种工具是可以同时使用的。就像用Google来搜索,用FaceBook进行社交一样,Hive可以用来进行统计查询,HBase可以用来进行实时查询,数据也可以从Hive写到Hbase,设置再从Hbase写回Hive。

posted @ 2017-07-21 11:44 鸿雁 阅读(141) | 评论 (0)编辑 收藏

Hive分布式安装配置Hive+Mysql

     摘要: 目录 1,环境准备 2,安装Hive和配置环境变量 3,安装MySQL 4,在mysql上创建hive元数据库,并对hive进行授权 5,安装jar包到hive 6,配置hive-site.xml 7,元数据存储初始化 8,启动验证hive 9,报错及解决方法 1,环境准备: 准备好Hadoop集群,参照...  阅读全文

posted @ 2017-07-20 15:20 鸿雁 阅读(444) | 评论 (0)编辑 收藏

TCP/UDP区别以及UDP如何实现可靠传输

TCP和UDP是OSI模型中的运输层中的协议。TCP提供可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输。


UDP(User Datagram Protocol)

        UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。并且它是将应用程序发来的数据在收到的那一刻,立刻按照原样发送到网络上的一种机制。

        即使是出现网络拥堵的情况下,UDP也无法进行流量控制等避免网络拥塞的行为。此外,传输途中如果出现了丢包,UDO也不负责重发。甚至当出现包的到达顺序乱掉时也没有纠正的功能。如果需要这些细节控制,那么不得不交给由采用UDO的应用程序去处理。换句话说,UDP将部分控制转移到应用程序去处理,自己却只提供作为传输层协议的最基本功能。UDP有点类似于用户说什么听什么的机制,但是需要用户充分考虑好上层协议类型并制作相应的应用程序。


TCP(Transmission Control Protocol)

        TCP充分实现爱呢了数据传输时各种控制功能,可以进行丢包的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在UDP中都没有。此外,TCP作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。

        TCP通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。此处不一一叙述。


TCP与UDP如何加以区分使用?

        TCP用于在传输层有必要实现可靠性传输的情况。由于它是面向有连接并具备顺序控制、重发控制等机制的。所以它可以为应用提供可靠传输。

        另一方面,UDP主要用于那些对高速传输和实时性有较高要求的通信或广播通信。举一个IP电话进行通话的例子。如果使用TCP,数据在传送途中如果丢失会被重发,但是这样无法流畅地传输通话人的声音,会导致无法进行正常交流。而采用UDP,它不会进行重发处理。从而也就不会有声音大幅度延迟到达的问题。即使有部分数据丢失,也只是影响某一小部分的通话。此外,在多播与广播通信中也使用UDP而不是UDP。RIP、DHCP等基于广播的协议也要依赖于UDP。


TCP与UDP区别总结:

1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接

2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节

6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道


UDP如何实现可靠传输

由于在传输层UDP已经是不可靠的连接,那就要在应用层自己实现一些保障可靠传输的机制

简单来讲,要使用UDP来构建可靠的面向连接的数据传输,就要实现类似于TCP协议的

超时重传(定时器)

有序接受 (添加包序号)

应答确认 (Seq/Ack应答机制)

滑动窗口流量控制等机制 (滑动窗口协议)

等于说要在传输层的上一层(或者直接在应用层)实现TCP协议的可靠数据传输机制,比如使用UDP数据包+序列号,UDP数据包+时间戳等方法。

目前已经有一些实现UDP可靠传输的机制,比如

UDT(UDP-based Data Transfer Protocol

基于UDP的数据传输协议(UDP-based Data Transfer Protocol,简称UDT)是一种互联网数据传输协议。UDT的主要目的是支持高速广域网上的海量数据传输,而互联网上的标准数据传输协议TCP在高带宽长距离网络上性能很差。 顾名思义,UDT建于UDP之上,并引入新的拥塞控制和数据可靠性控制机制。UDT是面向连接的双向的应用层协议。它同时支持可靠的数据流传输和部分可靠的数据报传输。 由于UDT完全在UDP上实现,它也可以应用在除了高速数据传输之外的其它应用领域,例如点到点技术(P2P),防火墙穿透,多媒体数据传输等等。

posted @ 2017-07-19 22:25 鸿雁 阅读(441) | 评论 (0)编辑 收藏

一文读懂分布式数据库Hbase

一、

1、什么是Hbase

一个高可靠性、高性能、列存储、可伸缩、实时读写的分布式数据库系统。

适合于存储非结构化数据,基于列的而不是基于行的模式

如图:Hadoop生态中hbase与其他部分的关系。


2、关系数据库已经流行很多年,并且hadoop已经有了HDFS和MapReduce,为什么需要HBase?

Hadoop可以很好地解决大规模数据的离线批量处理问题,但是,受限于HadoopMapReduce编程框架的高延迟数据处理机制,使得Hadoop无法满足大规模数据实时处理应用的需求
HDFS面向批量访问模式,不是随机访问模式
传统的通用关系型数据库无法应对在数据规模剧增时导致的系统扩展性和性能问题(分库分表也不能很好解决)
传统关系数据库在数据结构变化时一般需要停机维护;空列浪费存储空间
因此,业界出现了一类面向半结构化数据存储和处理的高可扩展、低写入/查询延迟的系统,例如,键值数据库、文档数据库和列族数据库(如BigTableHBase等)
HBase已经成功应用于互联网服务领域和传统行业的众多在线式数据分析处理系统中
3、HBase与传统的关系数据库的区别

1)数据类型:关系数据库采用关系模型,具有丰富的数据类型和存储方式,HBase则采用了更加简单的数据模型,它把数据存储为未经解释的字符串
2)数据操作:关系数据库中包含了丰富的操作,其中会涉及复杂的多表连接。HBase操作则不存在复杂的表与表之间的关系,只有简单的插入、查询、删除、清空等,因为HBase在设计上就避免了复杂的表和表之间的关系
3)存储模式:关系数据库是基于行模式存储的。HBase是基于列存储的,每个列族都由几个文件保存,不同列族的文件是分离的
4)数据索引:关系数据库通常可以针对不同列构建复杂的多个索引,以提高数据访问性能。HBase只有一个索引——行键,通过巧妙的设计,HBase中的所有访问方法,或者通过行键访问,或者通过行键扫描,从而使得整个系统不会慢下来
5)数据维护:在关系数据库中,更新操作会用最新的当前值去替换记录中原来的旧值,旧值被覆盖后就不会存在。而HBase中执行更新操作时,并不会删除数据旧的版本,而是生成一个新的版本,旧有的版本仍然保留
6)可伸缩性:关系数据库很难实现横向扩展,纵向扩展的空间也比较有限。相反,HBase和BigTable这些分布式数据库就是为了实现灵活的水平扩展而开发的,能够轻易地通过在集群中增加或者减少硬件数量来实现性能的伸缩
二、Hbase数据模型

1、模型概述

HBase是一个稀疏、多维度、排序的映射表,这张表的索引是行键、列族、列限定符和时间戳
每个值是一个未经解释的字符串,没有数据类型
用户在表中存储数据,每一行都有一个可排序的行键和任意多的列
表在水平方向由一个或者多个列族组成,一个列族中可以包含任意多个列,同一个列族里面的数据存储在一起
列族支持动态扩展,可以很轻松地添加一个列族或列,无需预先定义列的数量以及类型,所有列均以字符串形式存储,用户需要自行进行数据类型转换
HBase中执行更新操作时,并不会删除数据旧的版本,而是生成一个新的版本,旧有的版本仍然保留(这是和HDFS只允许追加不允许修改的特性相关的)

2、数据坐标
HBase
中需要根据行键、列族、列限定符和时间戳来确定一个单元格,因此,可以视为一个“四维坐标”,即[行键,列族, 列限定符,时间戳]

[201505003,Info,email, 1174184619081]

xie@qq.com

[201505003,Info,email, 1174184620720]

you@163.com


3、概念视图

4、物理视图


三、HBase实现原理

1、HBase的实现包括三个主要的功能组件:

1)库函数:链接到每个客户端
2)一个Master主服务器
3)许多个Region服务器
主服务器Master负责管理和维护HBase表的分区信息,维护Region服务器列表,分配Region,负载均衡
Region服务器负责存储和维护分配给自己的Region,处理来自客户端的读写请求
客户端并不是直接从Master主服务器上读取数据,而是在获得Region的存储位置信息后,直接从Region服务器上读取数据
客户端并不依赖Master,而是通过Zookeeper来获得Region位置信息,大多数客户端甚至从来不和Master通信,这种设计方式使得Master负载很小
2、Region
开始只有一个
Region,后来不断分裂
Region拆分操作非常快,接近瞬间,因为拆分之后的Region读取的仍然是原存储文件,直到“合并”过程把存储文件异步地写到独立的文件之后,才会读取新文件
同一个Region不会被分拆到多个Region服务器
每个Region服务器存储10-1000Region

元数据表,又名.META.表,存储了RegionRegion服务器的映射关系
HBase表很大时, .META.表也会被分裂成多个Region
根数据表,又名-ROOT-表,记录所有元数据的具体位置
-ROOT-表只有唯一一个Region,名字是在程序中被写死的
Zookeeper文件记录了-ROOT-表的位置


客户端访问数据时的“三级寻址”

为了加速寻址,客户端会缓存位置信息,同时,需要解决缓存失效问题

寻址过程客户端只需要询问Zookeeper服务器,不需要连接Master服务器

3、HBase的三层结构中各层次的名称和作用

层次

名称

作用

第一层

Zookeper文件

记录了-ROOT-表的位置信息

第二层

-ROOT-

记录了.META.表的Region位置信息

-ROOT-表只能有一个Region。通过-ROOT-表,就可以访问.META.表中的数据

第三层

.META.

记录了用户数据表的Region位置信息,.META.表可以有多个Region,保存了HBase中所有用户数据表的Region位置信息

四、HBase运行机制
1、HBase系统架构


(1、客户端包含访问HBase的接口,同时在缓存中维护着已经访问过的Region位置信息,用来加快后续数据访问过程

(2、Zookeeper可以帮助选举出一个Master作为集群的总管,并保证在任何时刻总有唯一一个Master在运行,这就避免了Master的“单点失效”问题
Zookeeper是一个很好的集群管理工具,被大量用于分布式计算,提供配置维护、域名服务、分布式同步、组服务等。

(3. Master
主服务器Master主要负责表和Region的管理工作:
管理用户对表的增加、删除、修改、查询等操作
实现不同Region服务器之间的负载均衡
Region分裂或合并后,负责重新调整Region的分布
对发生故障失效的Region服务器上的Region进行迁移
(4. Region服务器
Region服务器是HBase中最核心的模块,负责维护分配给自己的Region,并响应用户的读写请求
2、Region


(1、用户读写数据过程
用户写入数据时,被分配到相应Region服务器去执行
用户数据首先被写入到MemStoreHlog
只有当操作写入Hlog之后,commit()调用才会将其返回给客户端
当用户读取数据时,Region服务器会首先访问MemStore缓存,如果找不到,再去磁盘上面的StoreFile中寻找
(2、缓存的刷新
系统会周期性地把MemStore缓存里的内容刷写到磁盘的StoreFile文件中,清空缓存,并在Hlog里面写入一个标记、
每次刷写都生成一个新的StoreFile文件,因此,每个Store包含多个StoreFile文件

每个Region服务器都有一个自己的HLog文件,每次启动都检查该文件,确认最近一次执行缓存刷新操作之后是否发生新的写入操作;如果发现更新,则先写入MemStore,再刷写到StoreFile,最后删除旧的Hlog文件,开始为用户提供服务
(3、StroreFile的合并
每次刷写都生成一个新的StoreFile,数量太多,影响查找速度
调用Store.compact()把多个合并成一个
合并操作比较耗费资源,只有数量达到一个阈值才启动合并
3、Store工作原理
StoreRegion服务器的核心
多个StoreFile合并成一个
触发分裂操作,1个父Region被分裂成两个子Region
单个StoreFile过大时,又
4、HLog工作原理
分布式环境必须要考虑系统出错。HBase采用HLog保证系统恢复
HBase系统为每个Region服务器配置了一个HLog文件,它是一种预写式日志(WriteAhead Log
用户更新数据必须首先写入日志后,才能写入MemStore缓存,并且,直到MemStore缓存内容对应的日志已经写入磁盘,该缓存内容才能被刷写到磁盘

Zookeeper会实时监测每个Region服务器的状态,当某个Region服务器发生故障时,Zookeeper会通知Master
Master首先会处理该故障Region服务器上面遗留的HLog文件,这个遗留的HLog文件中包含了来自多个Region对象的日志记录
系统会根据每条日志记录所属的Region对象对HLog数据进行拆分,分别放到相应Region对象的目录下,然后,再将失效的Region重新分配到可用的Region服务器中,并把与该Region对象相关的HLog日志记录也发送给相应的Region服务器
Region服务器领取到分配给自己的Region对象以及与之相关的HLog日志记录以后,会重新做一遍日志记录中的各种操作,把日志记录中的数据写入到MemStore缓存中,然后,刷新到磁盘的StoreFile文件中,完成数据恢复
共用日志优点:提高对表的写操作性能;缺点:恢复时需要分拆日志
五、HBase性能
1、行键(RowKey)

行键是按照字典序存储,因此,设计行键时,要充分利用这个排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。

举个例子:如果最近写入HBase表中的数据是最可能被访问的,可以考虑将时间戳作为行键的一部分,由于是字典序排序,所以可以使用Long.MAX_VALUE- timestamp作为行键,这样能保证新写入的数据在读取时可以被快速命中。

InMemory:创建表的时候,可以通过HColumnDescriptor.setInMemory(true)将表放到Region服务器的缓存中,保证在读取的时候被cache命中。

Max Version:创建表的时候,可以通过HColumnDescriptor.setMaxVersions(int maxVersions)设置表中数据的最大版本,如果只需要保存最新版本的数据,那么可以设置setMaxVersions(1)

Time To Live创建表的时候,可以通过HColumnDescriptor.setTimeToLive(inttimeToLive)设置表中数据的存储生命期,过期数据将自动被删除,例如如果只需要存储最近两天的数据,那么可以设置setTimeToLive(2* 24 * 60 * 60)

2、HBaseMaster默认基于WebUI服务端口为60010HBase region服务器默认基于WebUI服务端口为60030.如果master运行在名为master.foo.com的主机中,mater的主页地址就是http://master.foo.com:60010,用户可以通过Web浏览器输入这个地址查看该页面
可以查看HBase集群的当前状态
3、NoSQL区别于关系型数据库的一点就是NoSQL不使用SQL作为查询语言,至于为何在NoSQL数据存储HBase上提供SQL接口

易使用,减少编码

4、HBase只有一个针对行健的索引

访问HBase表中的行,只有三种方式:

通过单个行健访问
通过一个行健的区间来访问
全表扫描

总结:
1、HBase数据库是BigTable的开源实现,和BigTable一样,支持大规模海量数据,分布式并发数据处理效率极高,易于扩展且支持动态伸缩,适用于廉价设备
2、HBase可以支持NativeJava APIHBaseShellThriftGatewayHive等多种访问接口,可以根据具体应用场合选择相应访问方式
3、HBase实际上就是一个稀疏、多维、持久化存储的映射表,它采用行键、列键和时间戳进行索引,每个值都是未经解释的字符串。
4、HBase采用分区存储,一个大的表会被分拆许多个Region,这些Region会被分发到不同的服务器上实现分布式存储
5、HBase的系统架构包括客户端、Zookeeper服务器、Master主服务器、Region服务器。客户端包含访问HBase的接口;Zookeeper服务器负责提供稳定可靠的协同服务;Master主服务器主要负责表和Region的管理工作;Region服务器负责维护分配给自己的Region,并响应用户的读写请求

posted @ 2017-07-19 22:24 鸿雁 阅读(221) | 评论 (0)编辑 收藏

在python中获取mac和ip地址

python 获得本机MAC地址:
import uuid
     def get_mac_address():
     mac=uuid.UUID(int=uuid.getnode()).hex[-12:]
     return ":".join([mac[e:e+2] for e in range(0,11,2)])


python获取IP的方法:使用socket

import socket

myname=socket.getfqdn(socket.gethostname( ))
    myaddr=socket.gethostbyname(myname)
    print(myname)
    print(myaddr)

posted @ 2017-05-15 23:26 鸿雁 阅读(230) | 评论 (0)编辑 收藏

Spring—Quartz定时调度CronTrigger时间配置格式说明与实例

spring中使用Quartz时 时间配置例子:

<!-- 定义调用对象和调用对象的方法 end   -->
<!-- 定义调用时间 begin -->
<bean id="realweatherTime" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail">
<ref bean="realweatherTask" />
</property>
<property name="cronExpression">
<value>0 10/30 * * * ?</value><!-- 表示每小时的10,40时执行任务 -->
</property>
</bean>
<!-- 定义调用时间 end   -->


<!-- 定义调用对象和调用对象的方法 end   -->
<!-- 定义调用时间 begin -->
<bean id="weatherTime" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail">
<ref bean="weatherTask" />
</property>
<property name="cronExpression">
<!--  <value>0 30 8,13 * * ?</value>  --><!-- 表示每天的8:30和13:30时执行任务 -->
<value>0 0,30 0-23 * * ?</value><!---表示每天从0-23时中每时的整点或半点执行任务->
</property>
</bean>

<!-- 定义调用时间 end   -->1、   CronTrigger时间格式配置说明

CronTrigger配置格式:

格式: [秒] [分] [小时] [日] [月] [周] [年]

序号

说明

是否必填

允许填写的值

允许的通配符

1

0-59 

, - * /

2

0-59

, - * /

3

小时

0-23

, - * /

4

1-31

, - * ? / L W

5

1-12 or JAN-DEC

, - * /

6

1-7 or SUN-SAT

, - * ? / L #

7

empty 或 1970-2099

, - * /

通配符说明:

:表示所有值. 例如:在分的字段上设置 "*",表示每一分钟都会触发。
? 
:表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为"?" 具体设置为 0 0 0 10 * ?
- 
:表示区间。例如 在小时上设置 "10-12",表示 10,11,12点都会触发。

, :表示指定多个值,例如在周字段上设置 "MON,WED,FRI" 表示周一,周三和周五触发
:用于递增触发。如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)。 在月字段上设置'1/3'所示每月1号开始,每隔三天触发一次。
L :表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于"7"或"SAT"。如果在"L"前加上数字,则表示该数据的最后一个。

例如在周字段上设置"6L"这样的格式,则表示“本月最后一个星期五"

W :表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上设置"15W",表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 "1W",它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,"W"前只能设置具体的数字,不允许区间"-").

'L'和 'W'可以一组合使用。如果在日字段上设置"LW",则表示在本月的最后一个工作日触发

 

# :序号(表示每月的第几周星期几),例如在周字段上设置"6#3"表示在每月的第三个周星期六.注意如果指定"6#5",正好第五周没有星期六,则不会触发该配置(用在母亲节和父亲节再合适不过了)

周字段的设置,若使用英文字母是不区分大小写的 MON 与mon相同.

常用示例:

格式: [秒] [分] [小时] [日] [月] [周] [年]

0 0 12 * * ?           每天12点触发 
0 15 10 ? * *          每天10点15分触发
 
0 15 10 * * ?          每天10点15分触发
  
0 15 10 * * ? *        每天10点15分触发
  
0 15 10 * * ? 2005     2005年每天10点15分触发
 
0 * 14 * * ?           每天下午的 2点到2点59分每分触发
 
0 0/5 14 * * ?         每天下午的 2点到2点59分(整点开始,每隔5分触发
)  
0 0/5 14,18 * * ?        每天下午的 18点到18点59分(整点开始,每隔5分触发)

0 0-5 14 * * ?            每天下午的 2点到2点05分每分触发 
0 10,44 14 ? 3 WED        3月分每周三下午的 2点10分和2点44分触发
 
0 15 10 ? * MON-FRI       从周一到周五每天上午的10点15分触发
 
0 15 10 15 * ?            每月15号上午10点15分触发
 
0 15 10 L * ?             每月最后一天的10点15分触发
 
0 15 10 ? * 6L            每月最后一周的星期五的10点15分触发
 
0 15 10 ? * 6L 2002-2005  从2002年到2005年每月最后一周的星期五的10点15分触发

0 15 10 ? * 6#3           每月的第三周的星期五开始触发 
0 0 12 1/5 * ?            每月的第一个中午开始每隔5天触发一次
 
0 11 11 11 11 ?           每年的11月11号 11点11分触发(光棍节)

spring中使用Quartz时 时间配置例子:

<!-- 定义调用对象和调用对象的方法 end   -->
<!-- 定义调用时间 begin -->
<bean id="realweatherTime" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail">
<ref bean="realweatherTask" />
</property>
<property name="cronExpression">
<value>0 10/30 * * * ?</value><!-- 表示每小时的10,40时执行任务 -->
</property>
</bean>
<!-- 定义调用时间 end   -->


<!-- 定义调用对象和调用对象的方法 end   -->
<!-- 定义调用时间 begin -->
<bean id="weatherTime" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail">
<ref bean="weatherTask" />
</property>
<property name="cronExpression">
<!--  <value>0 30 8,13 * * ?</value>  --><!-- 表示每天的8:30和13:30时执行任务 -->
<value>0 0,30 0-23 * * ?</value><!---表示每天从0-23时中每时的整点或半点执行任务->
</property>
</bean>
<!-- 定义调用时间 end   -->


posted @ 2017-04-29 12:01 鸿雁 阅读(295) | 评论 (0)编辑 收藏

前端内容展示操作

1.有时表格内容太多,只显示部分,其余部分已省略号表示,用css处理如下:
.template td{
    word-break:keep-all;/* 不换行 */
    white-space:nowrap;/* 不换行 */
    overflow:hidden;/* 内容超出宽度时隐藏超出部分的内容 */
    text-overflow:ellipsis;/* 当对象内文本溢出时显示省略标记(...) ;需与overflow:hidden;一起使用。*/
}

template 是该表单所在的table class属性。

posted @ 2016-03-09 11:36 鸿雁 阅读(200) | 评论 (0)编辑 收藏

js 金额 用逗号 隔开数字格式化

代码如下:
引用

function fmoney(s, n)  
{  
   n = n > 0 && n <= 20 ? n : 2;  
   s = parseFloat((s + "").replace(/[^\d\.-]/g, "")).toFixed(n) + "";  
   var l = s.split(".")[0].split("").reverse(),  
   r = s.split(".")[1];  
   t = "";  
   for(i = 0; i < l.length; i ++ )  
   {  
      t += l[i] + ((i + 1) % 3 == 0 && (i + 1) != l.length ? "," : "");  
   }  
   return t.split("").reverse().join("") + "." + r;  
}


调用:fmoney("12345.675910", 3),返回12,345.676

还原函数:
引用

function rmoney(s)  
{  
   return parseFloat(s.replace(/[^\d\.-]/g, ""));  
}


示例(可保存一下代码为html文件,运行查看效果):
引用

<SCRIPT>  
function fmoney(s, n)  
{  
   n = n > 0 && n <= 20 ? n : 2;  
   s = parseFloat((s + "").replace(/[^\d\.-]/g, "")).toFixed(n) + "";  
   var l = s.split(".")[0].split("").reverse(),  
   r = s.split(".")[1];  
   t = "";  
   for(i = 0; i < l.length; i ++ )  
   {  
      t += l[i] + ((i + 1) % 3 == 0 && (i + 1) != l.length ? "," : "");  
   }  
   return t.split("").reverse().join("") + "." + r;  
}  
function rmoney(s)  
{  
   return parseFloat(s.replace(/[^\d\.-]/g, ""));  
}  
function g(id)  
{  
   return document.getElementById(id);  
}  
window.onload = function()  
{  
   var num,  
   txt = g("txt"),  
   txt2 = g("txt2"),  
   btn = g("btn"),  
   btn2 = g("btn2"),  
   span = g("span");  
   btn.onclick = function()  
   {  
      num = parseInt(g("num").value);  
      txt.value = fmoney(txt.value, num);  
      txt2.value = fmoney(txt2.value, num);  
   }  
   ;  
   btn2.onclick = function()  
   {  
      num = parseInt(g("num").value);  
      span.innerHTML = "=" + fmoney(rmoney(txt.value) + rmoney(txt2.value), num);  
   }  
   ;  
}  
;  
</SCRIPT>  
小数点位数:  
<select id="num">  
<option value="2">2</option>  
<option value="3">3</option>  
<option value="4">4</option>  
<option value="5">5</option>  
</select>  
<input type="text" id="txt" value="12345.675910"> +  
<input type="text" id="txt2" value="1223"> <span id="span"></span>  
<br>  
<input type="button" id="btn" value="格式化">  
<input type="button" id="btn2" value="相加">

posted @ 2015-09-27 10:23 鸿雁 阅读(223) | 评论 (0)编辑 收藏

BigDecimal用法详解

一、简介
Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数。在实际应用中,需要对更大或者更小的数进行运算和处理。float和double只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal。BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。


二、构造器描述 
BigDecimal(int)       创建一个具有参数所指定整数值的对象。 
BigDecimal(double) 创建一个具有参数所指定双精度值的对象。 
BigDecimal(long)    创建一个具有参数所指定长整数值的对象。 
BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象。

三、方法描述 
add(BigDecimal)        BigDecimal对象中的值相加,然后返回这个对象。 
subtract(BigDecimal) BigDecimal对象中的值相减,然后返回这个对象。 
multiply(BigDecimal)  BigDecimal对象中的值相乘,然后返回这个对象。 
divide(BigDecimal)     BigDecimal对象中的值相除,然后返回这个对象。 
toString()                将BigDecimal对象的数值转换成字符串。 
doubleValue()          将BigDecimal对象中的值以双精度数返回。 
floatValue()             将BigDecimal对象中的值以单精度数返回。 
longValue()             将BigDecimal对象中的值以长整数返回。 
intValue()               将BigDecimal对象中的值以整数返回。

四、格式化及例子
由于NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制。

以利用BigDecimal对货币和百分比格式化为例。首先,创建BigDecimal对象,进行BigDecimal的算术运算后,分别建立对货币和百分比格式化的引用,最后利用BigDecimal对象作为format()方法的参数,输出其格式化的货币值和百分比。

复制代码
public static void main(String[] args) {     
    NumberFormat currency = NumberFormat.getCurrencyInstance(); //建立货币格式化引用
    NumberFormat percent = NumberFormat.getPercentInstance(); //建立百分比格式化引用
    percent.setMaximumFractionDigits(3); //百分比小数点最多3位
    BigDecimal loanAmount = new BigDecimal("15000.48"); //贷款金额
    BigDecimal interestRate = new BigDecimal("0.008"); //利率
    BigDecimal interest = loanAmount.multiply(interestRate); //相乘
    System.out.println("贷款金额:\t" + currency.format(loanAmount));
    System.out.println("利率:\t" + percent.format(interestRate));
    System.out.println("利息:\t" + currency.format(interest)); }
复制代码

运行结果如下:

贷款金额:    ¥15,000.48 
利率:
0.8%
利息: ¥
120.00

五、BigDecimal比较
BigDecimal是通过使用compareTo(BigDecimal)来比较的,具体比较情况如下:

复制代码
public static void main(String[] args) {     
    BigDecimal a = new BigDecimal("1");
    BigDecimal b = new BigDecimal("2");
    BigDecimal c = new BigDecimal("1");
    int result1 = a.compareTo(b);
    int result2 = a.compareTo(c);
    int result3 = b.compareTo(a);
    System.out.println(result1);
    System.out.println(result2);
    System.out.println(result3);
}
复制代码

打印结果是:-1、0、1,即左边比右边数大,返回1,相等返回0,比右边小返回-1。
注意不能使用equals方法来比较大小。

使用BigDecimal的坏处是性能比double和float差,在处理庞大,复杂的运算时尤为明显,因根据实际需求决定使用哪种类型。

posted @ 2015-08-22 12:56 鸿雁 阅读(338) | 评论 (0)编辑 收藏

oracle列转行

如果你只是寻求多行转换成一列,比如把同一个id的某个字段col变成一行数据库,把多个col用逗号链接起来。下面几个SQL可以立竿见影。
《1》最简短的方式,使用WMSYS.WM_CONCAT:
SELECT id, REPLACE(wmsys.wm_concat(col), ',', '/') str
FROM Table1
GROUP BY id;
《2》使用sys_connect_by_path:
SELECT t.id id, MAX(substr(sys_connect_by_path(t.col, ','), 2)) str
FROM (SELECT id, col, row_number() over(PARTITION BY id ORDER BY col) rn
FROM Table1) t
START WITH rn = 1
CONNECT BY rn = PRIOR rn + 1
AND id = PRIOR id
GROUP BY t.id;
或者
SELECT t.id id, substr(sys_connect_by_path(t.col, ','), 2) str
FROM (SELECT id, col, row_number() over(PARTITION BY id ORDER BY col) rn
FROM Table1) t
WHERE connect_by_isleaf = 1
START WITH rn = 1
CONNECT BY rn = PRIOR rn + 1
AND id = PRIOR id;
《3》使用MODEL:
SELECT id, substr(str, 2) str FROM Table1
MODEL
RETURN UPDATED ROWS
PARTITION BY(ID)
DIMENSION BY(row_number() over(PARTITION BY ID ORDER BY col) AS rn)
MEASURES (CAST(col AS VARCHAR2(20)) AS str)
RULES UPSERT
ITERATE(3) UNTIL( presentv(str[iteration_number+2],1,0)=0)
(str[0] = str[0] || ',' || str[iteration_number+1])
ORDER BY 1;
 
 
下面是原文:
1.
概述
最近论坛很多人提的问题都与行列转换有关系,所以我对行列转换的相关知识做了一个总结,希望对大家有所帮助,同时有何错疏,恳请大家指出,我也是在写作过程中学习,算是一起和大家学习吧!
行列转换包括以下六种情况:
1)
列转行
2)
行转列
3)
多列转换成字符串
4)
多行转换成字符串
5)
字符串转换成多列
6)
字符串转换成多行
下面分别进行举例介绍。
首先声明一点,有些例子需要如下10g及以后才有的知识:
A.
掌握model子句
B.
正则表达式
C.
加强的层次查询
讨论的适用范围只包括8i,9i,10g及以后版本。
2.
列转行
CREATE TABLE t_col_row(
ID INT,
c1 VARCHAR2(10),
c2 VARCHAR2(10),
c3 VARCHAR2(10));
INSERT INTO t_col_row VALUES (1, 'v11', 'v21', 'v31');
INSERT INTO t_col_row VALUES (2, 'v12', 'v22', NULL);
INSERT INTO t_col_row VALUES (3, 'v13', NULL, 'v33');
INSERT INTO t_col_row VALUES (4, NULL, 'v24', 'v34');
INSERT INTO t_col_row VALUES (5, 'v15', NULL, NULL);
INSERT INTO t_col_row VALUES (6, NULL, NULL, 'v35');
INSERT INTO t_col_row VALUES (7, NULL, NULL, NULL);
COMMIT;
SELECT * FROM t_col_row;
2.1
UNION ALL
适用范围:8i,9i,10g及以后版本
SELECT id, 'c1' cn, c1 cv
FROM t_col_row
UNION ALL
SELECT id, 'c2' cn, c2 cv
FROM t_col_row
UNION ALL
SELECT id, 'c3' cn, c3 cv FROM t_col_row;
若空行不需要转换,只需加一个where条件,
WHERE COLUMN IS NOT NULL 即可。
2.2
MODEL
适用范围:10g及以后
SELECT id, cn, cv FROM t_col_row
MODEL
RETURN UPDATED ROWS
PARTITION BY (ID)
DIMENSION BY (0 AS n)
MEASURES ('xx' AS cn,'yyy' AS cv,c1,c2,c3)
RULES UPSERT ALL
(
cn[1] = 'c1',
cn[2] = 'c2',
cn[3] = 'c3',
cv[1] = c1[0],
cv[2] = c2[0],
cv[3] = c3[0]
)
ORDER BY ID,cn;
2.3
COLLECTION
适用范围:8i,9i,10g及以后版本
要创建一个对象和一个集合:
CREATE TYPE cv_pair AS OBJECT(cn VARCHAR2(10),cv VARCHAR2(10));
CREATE TYPE cv_varr AS VARRAY(8) OF cv_pair;
SELECT id, t.cn AS cn, t.cv AS cv
FROM t_col_row,
TABLE(cv_varr(cv_pair('c1', t_col_row.c1),
cv_pair('c2', t_col_row.c2),
cv_pair('c3', t_col_row.c3))) t
ORDER BY 1, 2;
3.
行转列
CREATE TABLE t_row_col AS
SELECT id, 'c1' cn, c1 cv
FROM t_col_row
UNION ALL
SELECT id, 'c2' cn, c2 cv
FROM t_col_row
UNION ALL
SELECT id, 'c3' cn, c3 cv FROM t_col_row;
SELECT * FROM t_row_col ORDER BY 1,2;
3.1
AGGREGATE FUNCTION
适用范围:8i,9i,10g及以后版本
SELECT id,
MAX(decode(cn, 'c1', cv, NULL)) AS c1,
MAX(decode(cn, 'c2', cv, NULL)) AS c2,
MAX(decode(cn, 'c3', cv, NULL)) AS c3
FROM t_row_col
GROUP BY id
ORDER BY 1;
MAX聚集函数也可以用sum、min、avg等其他聚集函数替代。
被指定的转置列只能有一列,但固定的列可以有多列,请看下面的例子:
SELECT mgr, deptno, empno, ename FROM emp ORDER BY 1, 2;
SELECT mgr,
deptno,
MAX(decode(empno, '7788', ename, NULL)) "7788",
MAX(decode(empno, '7902', ename, NULL)) "7902",
MAX(decode(empno, '7844', ename, NULL)) "7844",
MAX(decode(empno, '7521', ename, NULL)) "7521",
MAX(decode(empno, '7900', ename, NULL)) "7900",
MAX(decode(empno, '7499', ename, NULL)) "7499",
MAX(decode(empno, '7654', ename, NULL)) "7654"
FROM emp
WHERE mgr IN (7566, 7698)
AND deptno IN (20, 30)
GROUP BY mgr, deptno
ORDER BY 1, 2;
这里转置列为empno,固定列为mgr,deptno。
还有一种行转列的方式,就是相同组中的行值变为单个列值,但转置的行值不变为列名:
ID CN_1 CV_1 CN_2 CV_2 CN_3 CV_3
1 c1 v11 c2 v21 c3 v31
2 c1 v12 c2 v22 c3
3 c1 v13 c2 c3 v33
4 c1 c2 v24 c3 v34
5 c1 v15 c2 c3
6 c1 c2 c3 v35
7 c1 c2 c3
这种情况可以用分析函数实现:
SELECT id,
MAX(decode(rn, 1, cn, NULL)) cn_1,
MAX(decode(rn, 1, cv, NULL)) cv_1,
MAX(decode(rn, 2, cn, NULL)) cn_2,
MAX(decode(rn, 2, cv, NULL)) cv_2,
MAX(decode(rn, 3, cn, NULL)) cn_3,
MAX(decode(rn, 3, cv, NULL)) cv_3
FROM (SELECT id,
cn,
cv,
row_number() over(PARTITION BY id ORDER BY cn, cv) rn
FROM t_row_col)
GROUP BY ID;
3.2
PL/SQL
适用范围:8i,9i,10g及以后版本
这种对于行值不固定的情况可以使用。
下面是我写的一个包,包中
p_rows_column_real用于前述的第一种不限定列的转换;
p_rows_column用于前述的第二种不限定列的转换。
CREATE OR REPLACE PACKAGE pkg_dynamic_rows_column AS
TYPE refc IS REF CURSOR;
PROCEDURE p_print_sql(p_txt VARCHAR2);
FUNCTION f_split_str(p_str VARCHAR2, p_division VARCHAR2, p_seq INT)
RETURN VARCHAR2;
PROCEDURE p_rows_column(p_table IN VARCHAR2,
p_keep_cols IN VARCHAR2,
p_pivot_cols IN VARCHAR2,
p_where IN VARCHAR2 DEFAULT NULL,
p_refc IN OUT refc);
PROCEDURE p_rows_column_real(p_table IN VARCHAR2,
p_keep_cols IN VARCHAR2,
p_pivot_col IN VARCHAR2,
p_pivot_val IN VARCHAR2,
p_where IN VARCHAR2 DEFAULT NULL,
p_refc IN OUT refc);
END;
/
CREATE OR REPLACE PACKAGE BODY pkg_dynamic_rows_column AS
PROCEDURE p_print_sql(p_txt VARCHAR2) IS
v_len INT;
BEGIN
v_len := length(p_txt);
FOR i IN 1 .. v_len / 250 + 1 LOOP
dbms_output.put_line(substrb(p_txt, (i - 1) * 250 + 1, 250));
END LOOP;
END;
FUNCTION f_split_str(p_str VARCHAR2, p_division VARCHAR2, p_seq INT)
RETURN VARCHAR2 IS
v_first INT;
v_last INT;
BEGIN
IF p_seq < 1 THEN
RETURN NULL;
END IF;
IF p_seq = 1 THEN
IF instr(p_str, p_division, 1, p_seq) = 0 THEN
RETURN p_str;
ELSE
RETURN substr(p_str, 1, instr(p_str, p_division, 1) - 1);
END IF;
ELSE
v_first := instr(p_str, p_division, 1, p_seq - 1);
v_last := instr(p_str, p_division, 1, p_seq);
IF (v_last = 0) THEN
IF (v_first > 0) THEN
RETURN substr(p_str, v_first + 1);
ELSE
RETURN NULL;
END IF;
ELSE
RETURN substr(p_str, v_first + 1, v_last - v_first - 1);
END IF;
END IF;
END f_split_str;
PROCEDURE p_rows_column(p_table IN VARCHAR2,
p_keep_cols IN VARCHAR2,
p_pivot_cols IN VARCHAR2,
p_where IN VARCHAR2 DEFAULT NULL,
p_refc IN OUT refc) IS
v_sql VARCHAR2(4000);
TYPE v_keep_ind_by IS TABLE OF VARCHAR2(4000) INDEX BY BINARY_INTEGER;
v_keep v_keep_ind_by;
TYPE v_pivot_ind_by IS TABLE OF VARCHAR2(4000) INDEX BY BINARY_INTEGER;
v_pivot v_pivot_ind_by;
v_keep_cnt INT;
v_pivot_cnt INT;
v_max_cols INT;
v_partition VARCHAR2(4000);
v_partition1 VARCHAR2(4000);
v_partition2 VARCHAR2(4000);
BEGIN
v_keep_cnt := length(p_keep_cols) - length(REPLACE(p_keep_cols, ',')) + 1;
v_pivot_cnt := length(p_pivot_cols) -
length(REPLACE(p_pivot_cols, ',')) + 1;
FOR i IN 1 .. v_keep_cnt LOOP
v_keep(i) := f_split_str(p_keep_cols, ',', i);
END LOOP;
FOR j IN 1 .. v_pivot_cnt LOOP
v_pivot(j) := f_split_str(p_pivot_cols, ',', j);
END LOOP;
v_sql := 'select max(count(*)) from ' || p_table || ' group by ';
FOR i IN 1 .. v_keep.LAST LOOP
v_sql := v_sql || v_keep(i) || ',';
END LOOP;
v_sql := rtrim(v_sql, ',');
EXECUTE IMMEDIATE v_sql
INTO v_max_cols;
v_partition := 'select ';
FOR x IN 1 .. v_keep.COUNT LOOP
v_partition1 := v_partition1 || v_keep(x) || ',';
END LOOP;
FOR y IN 1 .. v_pivot.COUNT LOOP
v_partition2 := v_partition2 || v_pivot(y) || ',';
END LOOP;
v_partition1 := rtrim(v_partition1, ',');
v_partition2 := rtrim(v_partition2, ',');
v_partition := v_partition || v_partition1 || ',' || v_partition2 ||
', row_number() over (partition by ' || v_partition1 ||
' order by ' || v_partition2 || ') rn from ' || p_table;
v_partition := rtrim(v_partition, ',');
v_sql := 'select ';
FOR i IN 1 .. v_keep.COUNT LOOP
v_sql := v_sql || v_keep(i) || ',';
END LOOP;
FOR i IN 1 .. v_max_cols LOOP
FOR j IN 1 .. v_pivot.COUNT LOOP
v_sql := v_sql || ' max(decode(rn,' || i || ',' || v_pivot(j) ||
',null))' || v_pivot(j) || '_' || i || ',';
END LOOP;
END LOOP;
IF p_where IS NOT NULL THEN
v_sql := rtrim(v_sql, ',') || ' from (' || v_partition || ' ' ||
p_where || ') group by ';
ELSE
v_sql := rtrim(v_sql, ',') || ' from (' || v_partition ||
') group by ';
END IF;
FOR i IN 1 .. v_keep.COUNT LOOP
v_sql := v_sql || v_keep(i) || ',';
END LOOP;
v_sql := rtrim(v_sql, ',');
p_print_sql(v_sql);
OPEN p_refc FOR v_sql;
EXCEPTION
WHEN OTHERS THEN
OPEN p_refc FOR
SELECT 'x' FROM dual WHERE 0 = 1;
END;
PROCEDURE p_rows_column_real(p_table IN VARCHAR2,
p_keep_cols IN VARCHAR2,
p_pivot_col IN VARCHAR2,
p_pivot_val IN VARCHAR2,
p_where IN VARCHAR2 DEFAULT NULL,
p_refc IN OUT refc) IS
v_sql VARCHAR2(4000);
TYPE v_keep_ind_by IS TABLE OF VARCHAR2(4000) INDEX BY BINARY_INTEGER;
v_keep v_keep_ind_by;
TYPE v_pivot_ind_by IS TABLE OF VARCHAR2(4000) INDEX BY BINARY_INTEGER;
v_pivot v_pivot_ind_by;
v_keep_cnt INT;
v_group_by VARCHAR2(2000);
BEGIN
v_keep_cnt := length(p_keep_cols) - length(REPLACE(p_keep_cols, ',')) + 1;
FOR i IN 1 .. v_keep_cnt LOOP
v_keep(i) := f_split_str(p_keep_cols, ',', i);
END LOOP;
v_sql := 'select ' || 'cast(' || p_pivot_col ||
' as varchar2(200)) as ' || p_pivot_col || ' from ' || p_table ||
' group by ' || p_pivot_col;
EXECUTE IMMEDIATE v_sql BULK COLLECT
INTO v_pivot;
FOR i IN 1 .. v_keep.COUNT LOOP
v_group_by := v_group_by || v_keep(i) || ',';
END LOOP;
v_group_by := rtrim(v_group_by, ',');
v_sql := 'select ' || v_group_by || ',';
FOR x IN 1 .. v_pivot.COUNT LOOP
v_sql := v_sql || ' max(decode(' || p_pivot_col || ',' || chr(39) ||
v_pivot(x) || chr(39) || ',' || p_pivot_val ||
',null)) as "' || v_pivot(x) || '",';
END LOOP;
v_sql := rtrim(v_sql, ',');
IF p_where IS NOT NULL THEN
v_sql := v_sql || ' from ' || p_table || p_where || ' group by ' ||
v_group_by;
ELSE
v_sql := v_sql || ' from ' || p_table || ' group by ' || v_group_by;
END IF;
p_print_sql(v_sql);
OPEN p_refc FOR v_sql;
EXCEPTION
WHEN OTHERS THEN
OPEN p_refc FOR
SELECT 'x' FROM dual WHERE 0 = 1;
END;
END;
/
4.
多列转换成字符串
CREATE TABLE t_col_str AS
SELECT * FROM t_col_row;
这个比较简单,用||或concat函数可以实现:
SELECT concat('a','b') FROM dual;
4.1
|| OR CONCAT
适用范围:8i,9i,10g及以后版本
SELECT * FROM t_col_str;
SELECT ID,c1||','||c2||','||c3 AS c123
FROM t_col_str;
5.
多行转换成字符串
CREATE TABLE t_row_str(
ID INT,
col VARCHAR2(10));
INSERT INTO t_row_str VALUES(1,'a');
INSERT INTO t_row_str VALUES(1,'b');
INSERT INTO t_row_str VALUES(1,'c');
INSERT INTO t_row_str VALUES(2,'a');
INSERT INTO t_row_str VALUES(2,'d');
INSERT INTO t_row_str VALUES(2,'e');
INSERT INTO t_row_str VALUES(3,'c');
COMMIT;
SELECT * FROM t_row_str;
5.1
MAX + DECODE
适用范围:8i,9i,10g及以后版本
SELECT id,
MAX(decode(rn, 1, col, NULL)) ||
MAX(decode(rn, 2, ',' || col, NULL)) ||
MAX(decode(rn, 3, ',' || col, NULL)) str
FROM (SELECT id,
col,
row_number() over(PARTITION BY id ORDER BY col) AS rn
FROM t_row_str) t
GROUP BY id
ORDER BY 1;
5.2
ROW_NUMBER + LEAD
适用范围:8i,9i,10g及以后版本
SELECT id, str
FROM (SELECT id,
row_number() over(PARTITION BY id ORDER BY col) AS rn,
col || lead(',' || col, 1) over(PARTITION BY id ORDER BY col) ||
lead(',' || col, 2) over(PARTITION BY id ORDER BY col) ||
lead(',' || col, 3) over(PARTITION BY id ORDER BY col) AS str
FROM t_row_str)
WHERE rn = 1
ORDER BY 1;
5.3
MODEL
适用范围:10g及以后版本
SELECT id, substr(str, 2) str FROM t_row_str
MODEL
RETURN UPDATED ROWS
PARTITION BY(ID)
DIMENSION BY(row_number() over(PARTITION BY ID ORDER BY col) AS rn)
MEASURES (CAST(col AS VARCHAR2(20)) AS str)
RULES UPSERT
ITERATE(3) UNTIL( presentv(str[iteration_number+2],1,0)=0)
(str[0] = str[0] || ',' || str[iteration_number+1])
ORDER BY 1;
5.4
SYS_CONNECT_BY_PATH
适用范围:8i,9i,10g及以后版本
SELECT t.id id, MAX(substr(sys_connect_by_path(t.col, ','), 2)) str
FROM (SELECT id, col, row_number() over(PARTITION BY id ORDER BY col) rn
FROM t_row_str) t
START WITH rn = 1
CONNECT BY rn = PRIOR rn + 1
AND id = PRIOR id
GROUP BY t.id;
适用范围:10g及以后版本
SELECT t.id id, substr(sys_connect_by_path(t.col, ','), 2) str
FROM (SELECT id, col, row_number() over(PARTITION BY id ORDER BY col) rn
FROM t_row_str) t
WHERE connect_by_isleaf = 1
START WITH rn = 1
CONNECT BY rn = PRIOR rn + 1
AND id = PRIOR id;
5.5
WMSYS.WM_CONCAT
适用范围:10g及以后版本
这个函数预定义按','分隔字符串,若要用其他符号分隔可以用,replace将','替换。
SELECT id, REPLACE(wmsys.wm_concat(col), ',', '/') str
FROM t_row_str
GROUP BY id;
6.
字符串转换成多列
其实际上就是一个字符串拆分的问题。
CREATE TABLE t_str_col AS
SELECT ID,c1||','||c2||','||c3 AS c123
FROM t_col_str;
SELECT * FROM t_str_col;
6.1
SUBSTR + INSTR
适用范围:8i,9i,10g及以后版本
SELECT id,
c123,
substr(c123, 1, instr(c123 || ',', ',', 1, 1) - 1) c1,
substr(c123,
instr(c123 || ',', ',', 1, 1) + 1,
instr(c123 || ',', ',', 1, 2) - instr(c123 || ',', ',', 1, 1) - 1) c2,
substr(c123,
instr(c123 || ',', ',', 1, 2) + 1,
instr(c123 || ',', ',', 1, 3) - instr(c123 || ',', ',', 1, 2) - 1) c3
FROM t_str_col
ORDER BY 1;
6.2
REGEXP_SUBSTR
适用范围:10g及以后版本
SELECT id,
c123,
rtrim(regexp_substr(c123 || ',', '.*?' || ',', 1, 1), ',') AS c1,
rtrim(regexp_substr(c123 || ',', '.*?' || ',', 1, 2), ',') AS c2,
rtrim(regexp_substr(c123 || ',', '.*?' || ',', 1, 3), ',') AS c3
FROM t_str_col
ORDER BY 1;
7.
字符串转换成多行
CREATE TABLE t_str_row AS
SELECT id,
MAX(decode(rn, 1, col, NULL)) ||
MAX(decode(rn, 2, ',' || col, NULL)) ||
MAX(decode(rn, 3, ',' || col, NULL)) str
FROM (SELECT id,
col,
row_number() over(PARTITION BY id ORDER BY col) AS rn
FROM t_row_str) t
GROUP BY id
ORDER BY 1;
SELECT * FROM t_str_row;
7.1
UNION ALL
适用范围:8i,9i,10g及以后版本
SELECT id, 1 AS p, substr(str, 1, instr(str || ',', ',', 1, 1) - 1) AS cv
FROM t_str_row
UNION ALL
SELECT id,
2 AS p,
substr(str,
instr(str || ',', ',', 1, 1) + 1,
instr(str || ',', ',', 1, 2) - instr(str || ',', ',', 1, 1) - 1) AS cv
FROM t_str_row
UNION ALL
SELECT id,
3 AS p,
substr(str,
instr(str || ',', ',', 1, 1) + 1,
instr(str || ',', ',', 1, 2) - instr(str || ',', ',', 1, 1) - 1) AS cv
FROM t_str_row
ORDER BY 1, 2;
适用范围:10g及以后版本
SELECT id, 1 AS p, rtrim(regexp_substr(str||',', '.*?' || ',', 1, 1), ',') AS cv
FROM t_str_row
UNION ALL
SELECT id, 2 AS p, rtrim(regexp_substr(str||',', '.*?' || ',', 1, 2), ',') AS cv
FROM t_str_row
UNION ALL
SELECT id, 3 AS p, rtrim(regexp_substr(str||',', '.*?' || ',',1,3), ',') AS cv
FROM t_str_row
ORDER BY 1, 2;
7.2
VARRAY
适用范围:8i,9i,10g及以后版本
要创建一个可变数组:
CREATE OR REPLACE TYPE ins_seq_type IS VARRAY(8) OF NUMBER;
SELECT * FROM TABLE(ins_seq_type(1, 2, 3, 4, 5));
SELECT t.id,
c.column_value AS p,
substr(t.ca,
instr(t.ca, ',', 1, c.column_value) + 1,
instr(t.ca, ',', 1, c.column_value + 1) -
(instr(t.ca, ',', 1, c.column_value) + 1)) AS cv
FROM (SELECT id,
',' || str || ',' AS ca,
length(str || ',') - nvl(length(REPLACE(str, ',')), 0) AS cnt
FROM t_str_row) t
INNER JOIN TABLE(ins_seq_type(1, 2, 3)) c ON c.column_value <=
t.cnt
ORDER BY 1, 2;
7.3
SEQUENCE SERIES
这类方法主要是要产生一个连续的整数列,产生连续整数列的方法有很多,主要有:
CONNECT BY,ROWNUM+all_objects,CUBE等。
适用范围:8i,9i,10g及以后版本
SELECT t.id,
c.lv AS p,
substr(t.ca,
instr(t.ca, ',', 1, c.lv) + 1,
instr(t.ca, ',', 1, c.lv + 1) -
(instr(t.ca, ',', 1, c.lv) + 1)) AS cv
FROM (SELECT id,
',' || str || ',' AS ca,
length(str || ',') - nvl(length(REPLACE(str, ',')), 0) AS cnt
FROM t_str_row) t,
(SELECT LEVEL lv FROM dual CONNECT BY LEVEL <= 5) c
WHERE c.lv <= t.cnt
ORDER BY 1, 2;
SELECT t.id,
c.rn AS p,
substr(t.ca,
instr(t.ca, ',', 1, c.rn) + 1,
instr(t.ca, ',', 1, c.rn + 1) -
(instr(t.ca, ',', 1, c.rn) + 1)) AS cv
FROM (SELECT id,
',' || str || ',' AS ca,
length(str || ',') - nvl(length(REPLACE(str, ',')), 0) AS cnt
FROM t_str_row) t,
(SELECT rownum rn FROM all_objects WHERE rownum <= 5) c
WHERE c.rn <= t.cnt
ORDER BY 1, 2;
SELECT t.id,
c.cb AS p,
substr(t.ca,
instr(t.ca, ',', 1, c.cb) + 1,
instr(t.ca, ',', 1, c.cb + 1) -
(instr(t.ca, ',', 1, c.cb) + 1)) AS cv
FROM (SELECT id,
',' || str || ',' AS ca,
length(str || ',') - nvl(length(REPLACE(str, ',')), 0) AS cnt
FROM t_str_row) t,
(SELECT rownum cb FROM (SELECT 1 FROM dual GROUP BY CUBE(1, 2))) c
WHERE c.cb <= t.cnt
ORDER BY 1, 2;
适用范围:10g及以后版本
SELECT t.id,
c.lv AS p,
rtrim(regexp_substr(t.str || ',', '.*?' || ',', 1, c.lv), ',') AS cv
FROM (SELECT id,
str,
length(regexp_replace(str || ',', '[^' || ',' || ']', NULL)) AS cnt
FROM t_str_row) t
INNER JOIN (SELECT LEVEL lv FROM dual CONNECT BY LEVEL <= 5) c ON c.lv <= t.cnt
ORDER BY 1, 2;
7.4
HIERARCHICAL + DBMS_RANDOM
适用范围:10g及以后版本
SELECT id,
LEVEL AS p,
rtrim(regexp_substr(str || ',', '.*?' || ',', 1, LEVEL), ',') AS cv
FROM t_str_row
CONNECT BY id = PRIOR id
AND PRIOR dbms_random.VALUE IS NOT NULL
AND LEVEL <=
length(regexp_replace(str || ',', '[^' || ',' || ']', NULL))
ORDER BY 1, 2;
7.5
HIERARCHICAL + CONNECT_BY_ROOT
适用范围:10g及以后版本
SELECT id,
LEVEL AS p,
rtrim(regexp_substr(str || ',', '.*?' || ',', 1, LEVEL), ',') AS cv
FROM t_str_row
CONNECT BY id = connect_by_root id
AND LEVEL <=
length(regexp_replace(str || ',', '[^' || ',' || ']', NULL))
ORDER BY 1, 2;
7.6
MODEL
适用范围:10g及以后版本
SELECT id, p, cv FROM t_str_row
MODEL
RETURN UPDATED ROWS
PARTITION BY(ID)
DIMENSION BY( 0 AS p)
MEASURES( str||',' AS cv)
RULES UPSERT
(cv
[ FOR p
FROM 1 TO length(regexp_replace(cv[0],'[^'||','||']',null))

例子:
SELECT t.dutyname , substr(sys_connect_by_path(t.username, ','), 2) str
FROM (SELECT dutyname, username, row_number() over(PARTITION BY dutyname ORDER BY username) rn
FROM test) t
WHERE connect_by_isleaf = 1
START WITH rn = 1
CONNECT BY rn = PRIOR rn + 1
AND dutyname = PRIOR dutyname;


posted @ 2015-07-27 23:52 鸿雁 阅读(241) | 评论 (0)编辑 收藏

Https通讯原理

Https是什么? Https是基于安全目的的Http通道,其安全基础由SSL层来保证。最初由netscape公司研发,主要提供了通讯双方的身份认证和加密通信方法。现在广泛应用于互联网上安全敏感通讯。 Https与Http主要区别 协议基础不同:Https在Http下加入了SSL层, 通讯方式不同:Https在数据通信之前需要客户端、服务器进行握手(身份认证),建立连接后,传输数据经过加密,通信端口443。 Http传输数据不加密,明文,通信端口80。 SSL协议基础 SSL协议位于TCP/IP协议与各种应用层协议之间,本身又分为两层: SSL记录协议(SSL Record Protocol):建立在可靠传输层协议(TCP)之上,为上层协议提供数据封装、压缩、加密等基本功能。 SSL握手协议(SSL Handshake Procotol):在SSL记录协议之上,用于实际数据传输前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。 SSL协议通信过程 (1) 浏览器发送一个连接请求给服务器;服务器将自己的证书(包含服务器公钥S_PuKey)、对称加密算法种类及其他相关信息返回客户端; (2) 客户端浏览器检查服务器传送到CA证书是否由自己信赖的CA中心签发。若是,执行4步;否则,给客户一个警告信息:询问是否继续访问。 (3) 客户端浏览器比较证书里的信息,如证书有效期、服务器域名和公钥S_PK,与服务器传回的信息是否一致,如果一致,则浏览器完成对服务器的身份认证。 (4) 服务器要求客户端发送客户端证书(包含客户端公钥C_PuKey)、支持的对称加密方案及其他相关信息。收到后,服务器进行相同的身份认证,若没有通过验证,则拒绝连接; (5) 服务器根据客户端浏览器发送到密码种类,选择一种加密程度最高的方案,用客户端公钥C_PuKey加密后通知到浏览器; (6) 客户端通过私钥C_PrKey解密后,得知服务器选择的加密方案,并选择一个通话密钥key,接着用服务器公钥S_PuKey加密后发送给服务器; (7) 服务器接收到的浏览器传送到消息,用私钥S_PrKey解密,获得通话密钥key。 (8) 接下来的数据传输都使用该对称密钥key进行加密。 上面所述的是双向认证 SSL 协议的具体通讯过程,服务器和用户双方必须都有证书。由此可见,SSL协议是通过非对称密钥机制保证双方身份认证,并完成建立连接,在实际数据通信时通过对称密钥机制保障数据安全性

posted @ 2014-06-13 16:16 鸿雁 阅读(211) | 评论 (0)编辑 收藏

8种Nosql数据库系统对比

1. CouchDB
•所用语言: Erlang
•特点:DB一致性,易于使用
•使用许可: Apache
•协议: HTTP/REST
•双向数据复制,
•持续进行或临时处理,
•处理时带冲突检查,
•因此,采用的是master-master复制(见编注2)
•MVCC – 写操作不阻塞读操作
•可保存文件之前的版本
•Crash-only(可靠的)设计
•需要不时地进行数据压缩
•视图:嵌入式 映射/减少
•格式化视图:列表显示
•支持进行服务器端文档验证
•支持认证
•根据变化实时更新
•支持附件处理
•因此, CouchApps(独立的 js应用程序)
•需要 jQuery程序库
最佳应用场景:适用于数据变化较少,执行预定义查询,进行数据统计的应用程序。适用于需要提供数据版本支持的应用程序。 例如: CRM、CMS系统。
master-master复制对于多站点部署是非常有用的。 (编注2:master-master复制:是一种数据库同步方法,允许数据在一组计算机之间共享数据,
并且可以通过小组中任意成员在组内进行数据更新。)

2. Redis
•所用语言:C/C++
•特点:运行异常快
•使用许可: BSD
•协议:类 Telnet
•有硬盘存储支持的内存数据库,
•但自2.0版本以后可以将数据交换到硬盘(注意, 2.4以后版本不支持该特性!)
•Master-slave复制(见编注3)
•虽然采用简单数据或以键值索引的哈希表,但也支持复杂操作,例如 ZREVRANGEBYSCORE。
•INCR & co (适合计算极限值或统计数据)
•支持 sets(同时也支持 union/diff/inter)
•支持列表(同时也支持队列;阻塞式 pop操作)
•支持哈希表(带有多个域的对象)
•支持排序 sets(高得分表,适用于范围查询)
•Redis支持事务 •支持将数据设置成过期数据(类似快速缓冲区设计)
•Pub/Sub允许用户实现消息机制
最佳应用场景:适用于数据变化快且数据库大小可遇见(适合内存容量)的应用程序。 例如:股票价格、数据分析、实时数据搜集、实时通讯。
(编注3:Master-slave复制:如果同一时刻只有一台服务器处理所有的复制请求,这被称为 Master-slave复制,通常应用在需要提供高可用性的服务器集群。)

 3. MongoDB
•所用语言:C++
•特点:保留了SQL一些友好的特性(查询,索引)。
•使用许可: AGPL(发起者: Apache)
•协议: Custom, binary( BSON)
•Master/slave复制(支持自动错误恢复,使用 sets 复制)
•内建分片机制
•支持 javascript表达式查询
•可在服务器端执行任意的 javascript函数
•update-in-place支持比CouchDB更好
•在数据存储时采用内存到文件映射
•对性能的关注超过对功能的要求
•建议最好打开日志功能(参数 –journal)
•在32位操作系统上,数据库大小限制在约2.5Gb
•空数据库大约占 192Mb
•采用 GridFS存储大数据或元数据(不是真正的文件系统)
最佳应用场景:适用于需要动态查询支持;需要使用索引而不是 map/reduce功能;需要对大数据库有性能要求;需要使用 CouchDB但因为数据改变太频繁而占满内存的应用程序。 例如:你本打算采用 MySQL或 PostgreSQL,但因为它们本身自带的预定义栏让你望而却步。

4. Riak
 •所用语言:Erlang和C,以及一些Javascript
•特点:具备容错能力
•使用许可: Apache
•协议: HTTP/REST或者 custom binary
•可调节的分发及复制(N, R, W)
•用 JavaScript or Erlang在操作前或操作后进行验证和安全支持。
•使用JavaScript或Erlang进行 Map/reduce
•连接及连接遍历:可作为图形数据库使用
•索引:输入元数据进行搜索(1.0版本即将支持)
•大数据对象支持( Luwak)
•提供“开源”和“企业”两个版本
•全文本搜索,索引,通过 Riak搜索服务器查询( beta版)
•支持Masterless多站点复制及商业许可的 SNMP监控
最佳应用场景:适用于想使用类似 Cassandra(类似Dynamo)数据库但无法处理 bloat及复杂性的情况。适用于你打算做多站点复制,但又需要对单个站点的扩展性,可用性及出错处理有要求的情况。 例如:销售数据搜集,工厂控制系统;对宕机时间有严格要求;可以作为易于更新的 web服务器使用。
 
5. Membase
•所用语言: Erlang和C
•特点:兼容 Memcache,但同时兼具持久化和支持集群
•使用许可: Apache 2.0
•协议:分布式缓存及扩展
•非常快速(200k+/秒),通过键值索引数据
•可持久化存储到硬盘
•所有节点都是唯一的( master-master复制)
•在内存中同样支持类似分布式缓存的缓存单元
•写数据时通过去除重复数据来减少 IO
•提供非常好的集群管理 web界面
•更新软件时软无需停止数据库服务
•支持连接池和多路复用的连接代理
最佳应用场景:适用于需要低延迟数据访问,高并发支持以及高可用性的应用程序 例如:低延迟数据访问比如以广告为目标的应用,高并发的 web 应用比如网络游戏(例如 Zynga)

6. Neo4j
•所用语言: Java
•特点:基于关系的图形数据库
•使用许可: GPL,其中一些特性使用 AGPL/商业许可
•协议: HTTP/REST(或嵌入在 Java中)
•可独立使用或嵌入到 Java应用程序
•图形的节点和边都可以带有元数据
•很好的自带web管理功能
•使用多种算法支持路径搜索
•使用键值和关系进行索引
•为读操作进行优化
•支持事务(用 Java api)
•使用 Gremlin图形遍历语言
•支持 Groovy脚本
•支持在线备份,高级监控及高可靠性支持使用 AGPL/商业许可 最佳应用场景:适用于图形一类数据。这是 Neo4j与其他nosql数据库的最显著区别 例如:社会关系,公共交通网络,地图及网络拓谱

7. Cassandra
•所用语言: Java
•特点:对大型表格和 Dynamo支持得最好
•使用许可: Apache
•协议: Custom, binary (节约型)
•可调节的分发及复制(N, R, W)
•支持以某个范围的键值通过列查询
•类似大表格的功能:列,某个特性的列集合
•写操作比读操作更快
•基于 Apache分布式平台尽可能地 Map/reduce
•我承认对 Cassandra有偏见,一部分是因为它本身的臃肿和复杂性,也因为 Java的问题(配置,出现异常,等等)
最佳应用场景:当使用写操作多过读操作(记录日志)如果每个系统组建都必须用 Java编写(没有人因为选用 Apache的软件被解雇) 例如:银行业,金融业(虽然对于金融交易不是必须的,但这些产业对数据库的要求会比它们更大)写比读更快,所以一个自然的特性就是实时数据分析

8. HBase (配合 ghshephard使用)
•所用语言: Java
•特点:支持数十亿行X上百万列
•使用许可: Apache
•协议:HTTP/REST (支持 Thrift,见编注4)
•在 BigTable之后建模
•采用分布式架构 Map/reduce
•对实时查询进行优化
•高性能 Thrift网关
•通过在server端扫描及过滤实现对查询操作预判
•支持 XML, Protobuf, 和binary的HTTP
•Cascading, hive, and pig source and sink modules
•基于 Jruby( JIRB)的shell
•对配置改变和较小的升级都会重新回滚
•不会出现单点故障
•堪比MySQL的随机访问性能 最佳应用场景:适用于偏好BigTable:)并且需要对大数据进行随机、实时访问的场合。 例如: Facebook消息数据库(更多通用的用例即将出现)

posted @ 2014-05-23 11:19 鸿雁 阅读(297) | 评论 (0)编辑 收藏

Oracle 10g内存结构之共享池的相关知识及使用简介

SGA中的第三个组成部分是共享池。共享池是对sql ,pl/sql 程序进行语法分析、编译、执行的内存区域。共享池包括库缓冲区(library cache)、数据字典缓冲区(Data Directory Cache)用户全局区(User Global Area)。其中库缓冲区含有Sql 语句的分析码、执行计划;数据字典缓冲区含有从数据字典中得到的表、列定义、权限。用户全局区包含用户的MTS 会话信息。

共享池主要用于对SQL 、pl/sql 程序语句进行语法分析、编译、执行、所以,如果应用中药运行大量存储过程或包,则要增加共享池的尺寸。共享池的大小由参数SHARE_POOL_SIZE确定。要了解共享池大小,可以用以下方法:

方法一:

 

  1. ………  
  2. Shared_Pool_size = 52428800 
  3. ….. 

 

方法二:

 

  1. SQL> select name,value from v$parameter where name like ‘%size’; 

 

方法三:

 

  1. SQL> show parameter share_pool_size 

 

共享池应计算存储过程、包等的成功率。

可以查询数据字典 v$rowcache 了解数据字典的成功与失败次数。

 

  1. SQL> select sum(gets) “dictionary gets”,  
  2. Sum(getmisses) “dictionary cache getmisses”  
  3. From v$rowcache ; 

 

其中gets 表示读取某一类数据字典的成功次数,getsmisses 表示读取某一类数据字典的失败次数。此外还可以通过查询结果计算共享池中读取数据字典的成功率

 

  1. SQL> select parameter, get, getmisses, getmisses/(getmisses+gets)*100 “miss ratio”,  
  2. (1-    (sum(getmisses)/(sum(getmisses)+sum(gets)) ) ) *100 “hit ratio”  
  3. From v$rowcache  
  4. Where gets+getmisses<>0  
  5. Group by parameter,gets,getmisses; 

 

查询数据字典 v$librarycache 可以计算共享池中库缓存的失败率,结果应该小于1%。

 

  1. SQL>select sum(pins) “ total pins”, sum(reloads) “ total reloads”, sum(reloads)/sum(pins)*100 libarycache  from  v$librarycache; 

 

其中 total pins 表示驻留内存的次数, total reloads 表示重新加载到内存的次数,librarycache 表示失败率。

上面分析了系统全局区的三个组成部分-----数据缓冲区、日志缓冲区及共享池,如果要得到SGA的总大小,可以在SQL*Plus中使用show sga 命令。

SQL>show sga或查询数据字典

SQL> select * from v$sga;

如果要查询某个参数的大小,可以查询数据字典v_$sagstat,通过计算可以知道sga的使用空间与空闲空间的比。

 

  1. SQL>col OBJECT_NAME format a20  
  2. SQL> col 空闲空间百分比(%) format 90.99  
  3. SQL> select name,  
  4. Sgasize/1024/1024  “allocated(M)” ,  
  5. Bytes/1024        “空闲空间(k)” ,  
  6. Round(bytes/sagsize*100,2)  “空闲空间百分比(%)”  
  7. From ( select sum(bytes) sgasize from sys.v_$sgastat) s ,sys.v_$sgastat f  
  8. Where f.name=’free memory’ ;  

 

关于Oracle 10g内存结构之共享池的相关知识及使用方法就介绍到这里了,希望本次的介绍能够对您有所收获!

posted @ 2014-05-17 23:37 鸿雁 阅读(148) | 评论 (0)编辑 收藏

Oracle 10g内存结构之系统全局区简介

我们知道,内存结构是Oracle体系结构中最重要的部分之一。按照系统对内存使用方法的不同,可以分为系统全局区(SGA)、程序全局区(PGA)、排序区(Sort Area)、大池(Large Pool)、及java池(java Pool),本文我们先介绍一下Oracle 10g内存结构之系统全局区的内容,接下来我们就开始介绍这部分内容。

系统全局区(System Global Area)

它是一组为系统分配的内存共享结构,可以包含一个数据库实例的数据和控制信息。如果多个用户连接到一个实例,在实例的系统全局区中,数据可以被多个用户共享,所以又称共享全局区。系统全局区按其作用不同,可以分为数据缓冲区、日志缓冲区及共享池。

数据缓冲区:

数据缓冲区用于从磁盘读入的数据,供所有用户共享。

修改的数据、插入的数据存储在数据缓冲区中,修改完成或DBWR进程的其他条件引发时,数据被写入数据文件

数据缓冲区工作原理:

LRU (Least recently used):最近最少使用原则的缩写,是一种数据缓冲区的一种管理机制,,只保留最近数据,不保留旧数据。

Dirty:表示脏数据,脏数据是修改后还没有写到数据文件的数据。

Oracle10g 的数据库内存的设置参数不再由DB_BLOCK_BUFFERS确定,而是由oracle的新参数DB_CACHE_SIZE 和DB_nK_CACHE_SIZE确定,不同的数据段可以使用不同的数据块。大表可以存储在大的数据块表空间中,小表可以存储在小的数据块表空间中,以优化i/o性能。对于系统表空间、临时表空间、及其它默认设置的表空间,可以使用标准的数据块DB_BLOCK_SIZE确定。

标准数据块DB_BLOCK_SIZE用于系统表空间及默认表空间,其他表空间可以使用非标准数据块BLOCKSIZE(创建表空间时使用),其值分别为 2k 4k 8k 16k 32k ,非标准数据块的数据缓冲区使用参数DB_Nk_CACHE_SIZE确定。

需要注意的是BLOCKSIZE不得用于标准块。如果设置了DB_BLOCK_SIZE=2048,则不得设置DB_2K_CACHE_SIZE,标准块必须使用参数DB_CACHE_SIZE 来设置。同时可以在线修改数据缓冲区参数:SQL> alter system set db_2k_cache_size = 10M ;如果要查询数据缓冲区大小,可以如下:SQL> show parameter db。

在创建不同数据块表空间时,要使用参数BLOCKSIZE指出数据块的大小,同时在参数文件中要使用DB_Nk_CACHE_SIZE 进行配置,与BLOCKSIZE的个数相对应,否则会出现错误。

设置动态内存时,可以将多个参数全部写入参数文件,格式如下:

  1. # cache and i/o  
  2.  DB_BLOCK_SIZE=4096 
  3.  DB_CACHE_SIZE=20971520 
  4.  DB_2K_CACHE_SIZE=8M 
  5.  DB_8K_CACHE_SIZE=4M 
  6.  …….. 

其中,参数 DB_CACHE_SIZE 只适用于系统表空间、临时表空间、及默认表空间,DB_2K_CACHE_SIZE  适合 BLOCKSIZE 为2K的表空间。8K 也是一样的道理。

数据缓冲区对数据库德存取速度又直接影响。一般的缓冲区命中率应该在90% 以上。例如,使用数据字典 v$sysstat 计算数据缓冲区命中率:

  1. SQL> select a.value+b.value “logical_reads” , c.value “phys_reads”,  
  2.       Round(100* ( ( a.value+b.value)- c.value) /  
  3.       ( a.value+b.value ) ) “buffer hit radio “  
  4.       From v$sysstat a, v$sysstat b,v$sysstat c  
  5.       Where a.statistic#=38 and b.statistic#=39 and c.statistic#=40; 

下面是计算数据缓冲命中率的另一种方法:

  1. SQL> select name, value  
  2.       From v$sysstat  
  3.       Where name in ( ‘session logical reads’,’physical reads’,physical reads direct’, ‘physical reads direct (lob)’); 

其中:Session logical reads 为读的总量。Physical reads为从数据文件读。Physical reads direct 为从缓冲区读(不含lobs)。Physical reads direct (lobs) 为从缓冲区读(含lobs)。Hit Ratio = 1- ( ( physical reads- physical reads direct – physical reads direct(lob) ) /session logical reads) = 95%。

日志缓冲区

日志缓冲区用来存储数据库的修改信息。日志信息首先在日志缓冲区中产生,当日志缓冲区的日志达到一定数量时,由日志写入进程LGWR将日志数据写入日志文件组,再经过切换,由归档进程ARCH将日志数据写入归档介质。

日志缓冲区大小由参数LOG_BUFFER确定,要查询日志缓冲区大小可以用以下方法:

方法一:参数文件中:

  1.  ……  
  2.  Processes = 150 
  3.  Parallel_max_servers = 5 
  4. Log_buffer = 32768 
  5. …….. 

方法二:

  1. SQL> select name,value from v$parameter where name like ‘%buffer’; 

方法三:

  1. SQL> show parameter log_buffer 

对于日志缓冲区而言可以计算失败率,使用数据字典v$latch 计算日志缓冲区的失败率

  1. SQL>select name,gets,misses,immediate_gets,immediate_misses,  
  2.      Decode(gets,0,0,misses/gets*100) ratiol,  
  3.      Decode (immediate_gets+immediate_misses,0,0,  
  4. immediate_misses/(immediate_gets+immediate_misses)*100)   ratio2  
  5.      from v$latch  
  6.      where name in (‘redo allocation’, ‘redo copy’); 

其中

Gets 表示成功等待日志缓冲区的次数。

Immediate gets 表示成功立即得到日志缓冲区的次数。

Immediate misses 表示未成功立即得到日志缓冲区的次数。

等待表示日志在进入日志缓冲区时,因为日志缓冲区过小而没有空闲空间,所以日志缓冲区的失败可以表示日志缓冲区是否足够大,不够大时,用户的日志写将产生等待过程。日志缓冲区的失败率应该小于1%。

此外,可以查询用户进程等待日志缓冲区时的次数,通过数据字典v$sysstat 得到:

  1. SQL> select name,value from v$sysstat  
  2.       Where name = ‘ redo buffer allocation retries’ ; 

关于Oracle 10g内存结构之系统全局区的相关知识就介绍到这里了,希望本次的介绍能够对您有所收获!

posted @ 2014-05-17 23:31 鸿雁 阅读(177) | 评论 (0)编辑 收藏

Oracle数据库命中率优化原理解析

本文是关于Oracle数据库调试与优化方面的文章,主要介绍Oracle数据库中命中率相关的问题,包括不同的算法之间性能的比对。关于Oracle中各个命中率的计算以及相关的调优 1) Library Cache的命中率: 计算公式:Library Cache Hit Ratio = sum(pinhits) / sum(pi

本文是关于Oracle数据库调试与优化方面的文章,主要介绍Oracle数据库中命中率相关的问题,包括不同的算法之间性能的比对。关于Oracle中各个命中率的计算以及相关的调优

1)Library Cache的命中率:计算公式:Library Cache Hit Ratio = sum(pinhits) / sum(pins)

1
SELECT SUM(pinhits)/sum(pins) FROM V$LIBRARYCACHE;

通常在98%以上,否则,需要要考虑加大共享池,绑定变量,修改cursor_sharing等参数。


2)计算共享池内存使用率:

1
2
SELECT (1 - ROUND(BYTES / (&TSP_IN_M * 1024 * 1024), 2)) * 100 || '%'
FROM V$SGASTAT WHERE NAME = 'free memory' AND POOL = 'shared pool';

其中: &TSP_IN_M是你的总的共享池的SIZE(M)

共享池内存使用率,应该稳定在75%-90%间,太小浪费内存,太大则内存不足。

查询空闲的共享池内存:

1
2
SELECT * FROM V$SGASTAT WHERE
NAME = 'free memory' AND POOL = 'shared pool';

3)db buffer cache命中率:计算公式:Hit ratio = 1 - [physical reads/(block gets + consistent gets)]

1
2
3
SELECT NAME, PHYSICAL_READS, DB_BLOCK_GETS, CONSISTENT_GETS,
1 - (PHYSICAL_READS / (DB_BLOCK_GETS + CONSISTENT_GETS)) 
"Hit Ratio" FROM V$BUFFER_POOL_STATISTICS WHERE NAME='DEFAULT';

通常应在90%以上,否则,需要调整,加大DB_CACHE_SIZE

外一种计算命中率的方法(摘自ORACLE官方文档<<数据库性能优化>>):

命中率的计算公式为:

Hit Ratio = 1 - ((physical reads - physical reads direct - physical reads direct (lob)) / (db block gets + consistent gets - physical reads direct - physical reads direct (lob))

分别代入上一查询中的结果值,就得出了Buffer cache的命中率

1
2
3
4
SELECT NAME, VALUE FROM V$SYSSTAT WHERE NAME IN(
'session logical reads', 'physical reads'
'physical reads direct', 'physical reads direct (lob)'
'db block gets', 'consistent gets');

4)数据缓冲区命中率

1
2
3
4
5
SQL> select value from v$sysstat where name ='physical reads'
SQL> select value from v$sysstat where name ='physical reads direct'
SQL> select value from v$sysstat where name ='physical reads direct (lob)'
SQL> select value from v$sysstat where name ='consistent gets'
SQL> select value from v$sysstat where name = 'db block gets';

这里命中率的计算应该是令 x = physical reads direct + physical reads direct (lob),命中率 =100 - ( physical reads - x) / (consistent gets + db block gets - x)*100,通常如果发现命中率低于90%,则应该调整应用可可以考虑是否增大数据缓冲区

5)共享池的命中率:

1
2
select sum(pinhits-reloads)/sum(pins)*100 "hit radio"
from v$librarycache;

假如共享池的命中率低于95%,就要考虑调整应用(通常是没使用bind var )或者增加内存

6)计算在内存中排序的比率:

1
SELECT * FROM v$sysstat t WHERE NAME='sorts (memory)';

查询内存排序数 

1
SELECT * FROM v$sysstat t WHERE NAME='sorts (disk)';

查询磁盘排序数caculate sort in memory ratio 

1
SELECT round(&sort_in_memory/(&sort_in_memory+&sort_in_disk),4)*100||'%' FROM dual;

此比率越大越好,太小整要考虑调整,加大PGA

7)PGA的命中率:

计算公式:BP x 100 / (BP + EBP)

BP: bytes processed

EBP: extra bytes read/written

1
SELECT * FROM V$PGASTAT WHERE NAME='cache hit percentage';

或者从OEM的图形界面中查看

我们可以查看一个视图以获取Oracle的建议值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SELECT round(PGA_TARGET_FOR_ESTIMATE/1024/1024) target_mb,
ESTD_PGA_CACHE_HIT_PERCENTAGE cache_hit_perc, ESTD_OVERALLOC_COUNT 
FROM V$PGA_TARGET_ADVICE; 
The output of this query might look like the following: 
TARGET_MB CACHE_HIT_PERC ESTD_OVERALLOC_COUNT
 ---------- -------------- -------------------- 
63 23 367 
125 24 30 
250 30 3 
375 39 0 
500 58 0 
600 59 0 
700 59 0 
800 60 0 
900 60 0

在此例中:PGA至少要分配375M

我个人认为PGA命中率不应该低于50%

以下的SQL统计sql语句执行在三种模式的次数: optimal memory size, one-pass memory size, multi-pass memory size:

1
2
3
SELECT name profile, cnt, decode(total, 0, 0, round(cnt*100/total,4)) 
percentage FROM (SELECT name, value cnt, (sum(value) over ()) 
total FROM V$SYSSTAT WHERE name like 'workarea exec%');

8)共享区字典缓存区命中率

计算公式:SUM(gets - getmisses - usage -fixed) / SUM(gets)

命中率应大于0.85

1
select sum(gets-getmisses-usage-fixed)/sum(gets) from v$rowcache;

9)数据高速缓存区命中率

计算公式:1-(physical reads / (db block gets + consistent gets))

命中率应大于0.90最好

1
select name,value from v$sysstat where name in ('physical reads','db block gets','consistent gets');

10)共享区库缓存区命中率

计算公式:SUM(pins - reloads) / SUM(pins)

命中率应大于0.99

1
select sum(pins-reloads)/sum(pins) from v$librarycache;

11)检测回滚段的争用

SUM(waits)值应小于SUM(gets)值的1%

1
select sum(gets),sum(waits),sum(waits)/sum(gets) from v$rollstat;

12)检测回滚段收缩次数

1
2
select name,shrinks from v$rollstat, v$rollname 
where v$rollstat.usn = v$rollname.usn;

 

几个常用的检查语句

1. 查找排序最多的SQL:

1
2
SELECT HASH_VALUE, SQL_TEXT, SORTS, EXECUTIONS 
FROM V$SQLAREA ORDER BY SORTS DESC;


2.查找磁盘读写最多的SQL:

1
2
3
4
SELECT * FROM (SELECT sql_text,disk_reads "total disk"
executions "total exec",disk_reads/executions "disk/exec"
FROM v$sql WHERE executions>0 and is_obsolete='N' ORDER BY
4 desc) WHERE ROWNUM<11 ;


3.查找工作量最大的SQL(实际上也是按磁盘读写来排序的):

1
2
3
4
5
6
7
8
select substr(to_char(s.pct, '99.00'), 2) || '%' load,
s.executions executes,p.sql_text from(select address,disk_reads,executions,pct,rank() over 
(order by disk_reads desc) ranking from (select
address,disk_reads,executions,100 * ratio_to_report
(disk_reads) over () pct from sys.v_$sql where
command_type != 47) where disk_reads > 50 * executions) s,
ys.v_$sqltext p where s.ranking <= 5 and p.address = s.address 
order by 1, s.address, p.piece;

  

4. 用下列SQL工具找出低效SQL:

1
2
3
4
5
select executions,disk_reads,buffer_gets,round((buffer_gets-
disk_reads)/buffer_gets,2) Hit_radio,round(disk_reads/executions,2)
reads_per_run,sql_text From v$sqlarea Where executions>0 and
buffer_gets >0 and (buffer_gets-disk_reads)/buffer_gets<0.8 
Order by 4 desc;

  

5、根据sid查看对应连接正在运行的sql

1
2
3
4
5
6
7
select /*+ push_subq */command_type,sql_text,sharable_mem,persistent_mem,
runtime_mem,sorts,version_count,loaded_versions,open_versions,
users_opening,executions,users_executing,loads,first_load_time,
invalidations,parse_calls,disk_reads,buffer_gets,rows_processed,
sysdate start_time,sysdate finish_time,’>’||address 
sql_address,’N’status From v$sqlarea Where address=
(select sql_address from v$session where sid=&sid);


***************Oracle 缓冲区命中率低的分析及解决办法******************

首先确定下面的查询结果:

1,缓冲区命中率的查询(是否低于90%):

1
2
3
select round((1 - sum(decode(name,'physical reads',value,0)) / 
(sum(decode(name,'db block gets',value,0)) + sum(decode(name,'
consistent gets',value,0))) ),4) *100 || '%' chitrati from v$sysstat;

  

2,使用率的查询(有无free状态的数据快.):

1
select count(*), status from v$bh group by status ;

  

3,相关等待事件的查询(是否有相关等待事件)

1
2
select event,total_waits from v$system_event where event 
in ('free buffer waits');

  

4,当前大小(是否已经很大)

1
2
select value/1024/1024 cache_size from v$parameter 
where name='db_cache_size'


5,top等待事件分析(Db file scatered read的比率是否大)

1
2
3
4
5
6
7
select event ,total_waits,suml from (select
event,total_waits,round(total_waits/sumt*100,2)||'%' suml 
from (select event,total_waits from v$system_event ), 
(select sum(total_waits) sumt from v$system_event) 
order by total_waits desc) where rownum<6 and event 
not like 'rdbms%' and event not like 'pmon%' and event 
not like 'SQL*Net%' and event not like 'smon%';

  

6,db_cache_advice建议值(9i后的新特性,可以根据他更好的调整cache_size)

1
2
select block_size,size_for_estimate,size_factor,
estd_physical_reads from v$db_cache_advice;


说明分析:

缓冲区命中率(低于90的命中率就算比较低的).

没有free不一定说明需要增加,还要结合当前cache_size的大小(我们是否还可以再增大,是否有需要增加硬件,增加开销),

空闲缓冲区等待说明进程找不到空闲缓冲区,并通过写出灰缓冲区,来加速数据库写入器生成空闲缓冲区,当DBWn将块写入磁盘后,灰数据缓冲区将被释放,以便重新使用.产生这种原因主要是:

1,DBWn可能跟不上写入灰缓冲区:i/0系统较慢,尽量将文件均匀的分布于所有设备,

2,缓冲区过小或过大。

3,可以增加db_writer_processes数量。

4,可能有很大的一个事物,或者连续的大事物

我们需要长期观察这个事件是否长期存在并数值一直在增大,如果一直在增大,则说明需要增大db_cache大小.或优化sql.

数据分散读等待,通常表现存在着与全表扫描相关的等待,逻辑读时,在内存中进行的全表扫描一般是零散地,而并非连续的被分散到缓冲区的各个部分,可能有索引丢失,或被仰制索引的存在。该等待时间在数据库会话等待多块io读取结束的时候产生,并把指定的块数离散的分布在数据缓冲区。这意味这全表扫描过多,或者io不足或争用,

存在这个事件,多数都是问题的,这说明大量的全部扫描而未采用索引.

db_cache_advice对我们调整db_cache_size大小有一定的帮助,但这只是一个参考,不一定很精确。

通过上面6种情况的综合分析,判断是否需要增加大cache_size. 或者把常用的(小)表放到keep区。

但多数的时候做这些不会解决质的问题,

而真正的问题主要是对sql语句的优化(如:是否存在大量的全表扫描等)

索引是在不需要改变程序的情况下,对数据库性能,sql语句提高的最实用的方法.

我在生产中遇到过类似的问题,200M的cache_size,命中率很低21%,但通过对sql语句的优化(添加索引,避免全表扫描),命中率增加到96%,程序运行时间由原来的2小时减少到不到10分钟.

这就提到了怎么定位高消耗的sql问题.全表扫描的问题,在这里不做细致的解说,这里只说明方法,我会在相关的章节专门介绍怎么使用这些工具

1,sql_trace跟踪session.用tkprof 分别输出磁盘读,逻辑读,运行时间长的sql进行优化.这些高消耗的sql一般都伴随着全表扫描.

2,statspack分析.在系统繁忙时期进行时间点的统计分析,产看TOP事件是否有Db file scatered read.并查看TOP sql语句是否存在问题等.

还要说一句:当然在硬件允许的情况下,尽量增大db_cache_size 减少磁盘读,但并不是越大越好,一定要根据自己的库数据量的程度来调节,因为大的db_cache_size同样会增大数据库管理的开销,当然可能开销并不会明显的影响数据库的性能,硬件价格也越来越低,这就需要我们具体问题具体分析了,在我看来物尽其用就最好了,尽量不要浪费,找到问题的本质。调优是一件很艺术的事。

***********************Oracle数据库缓冲区命中率*****************

1、查看Oracle数据库缓冲区命中率

1
2
3
4
select a.value + b.value "logical_reads", c.value "phys_reads"
round(100 * ((a.value+b.value)-c.value) / (a.value+b.value)) 
"BUFFER HIT RATIO" from v$sysstat a, v$sysstat b, v$sysstat c 
where a.statistic# = 40 and b.statistic# = 41 and c.statistic# = 42;


2、Tags: oracle

数据库缓冲区命中率:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
select value from v$sysstat where name ='physical reads'
value 3714179 
   
select value from v$sysstat where name ='physical reads direct'
value 0 
   
select value from v$sysstat where name ='physical reads direct(lob)';
value 0 
   
select value from v$sysstat where name ='consistent gets'
value 856309623 
   
select value from v$sysstat where name ='db block gets'
value 19847790

这里命中率的计算应该是

令x=physical reads direct + physical reads direct(lob)

命中率=100-(physical reads -x)/(consistent gets +db block gets -x)*100

通常如果发现命中率低于90%,则应该调整应用可以考虑是否增大数据加


共享池的命中率

1
select sum(pinhits)/sum(pins)*100 "hit radio" from v$librarycache;

如果共享池的命中率低于95%就要考虑调整应用(通常是没应用bind var)或者增加内存。

关于排序部分

1
select name,value from v$sysstat where name like '%sort%';

如果我们发现sorts(disk)/(sorts(memory)+sorts(disk))的比例过高,则通常意味着sort_area_size部分内存教较小,可考虑调整相应的参数。

  

关于log_buffer

1
2
select name,value from v$sysstat where name
in ('redo entries','redo buffer allocation retries');

假如redo buffer allocation retries/redo entries的比例超过1%我们就可以考虑增加log_buffer.

posted @ 2014-05-16 21:14 鸿雁 阅读(235) | 评论 (0)编辑 收藏

深入理解Oracle Statistic统计信息

1.统计信息简介 统计信息主要是描述数据库中表,索引的大小,规模,数据分布状况等的一类信息。比如,表的行数,块数,平均每行的大小,索引的leaf blocks,索引字段的行数,不同值的大小等,都属于统计信息。CBO正是根据这些统计信息数据,计算出不同访问路

1.统计信息简介

统计信息主要是描述数据库中表,索引的大小,规模,数据分布状况等的一类信息。比如,表的行数,块数,平均每行的大小,索引的leaf blocks,索引字段的行数,不同值的大小等,都属于统计信息。CBO正是根据这些统计信息数据,计算出不同访问路径下,不同join 方式下,各种计划的成本,最后选择出成本最小的计划。

在CBO(基于代价的优化器模式)条件下,SQL语句的执行计划由统计信息来决定,若没有统计信息则会采取动态采样的方式决定执行计划!可以说统计信息关乎sql的执行计划是否正确,属于sql执行的指导思想,oracle的初始化参数statistics_level控制收集统计信息的级别,有三个参数值:

BASIC :收集基本的统计信息

TYPICAL:收集大部分统计信息(数据库的默认设置)

ALL:收集全部统计信息

Oracle 10g之后,Query Optimizer就已经将CBO作为默认优化器,并且Oracle官方不再支持RBO服务。但是,通过优化器参数optimizer_mode,我们可以控制Oracle优化器生成不同模式下的执行计划。

关于优化器的请参考:《SQL性能优化之optimizer_mode参数原理渗透解析

2.如何收集统计信息

2.1 统计信息的内容:

1)Table statistics

Number of rows --行数量

Number of blocks --block数量

Average row length --平均行的长度.

2)Column statistics

Number of distinct values (NDV) in column --列中distinct的值

Number of nulls in column --列中null的值

Data distribution (histogram)  --数据分布

3)Index statistics

Number of leaf blocks --子节点的块数量

Levels --子节点数量

Clustering factor --集群因子

4)System statistics

I/O performance and utilization --IO性能和利用率

CPU performance and utilization --CPU的性能和利用率

2.2 收集统计信息

Oracle Statistic 的收集,可以使用analyze 命令,也可以使用DBMS_STATS 包来收集,Oracle 建议使用DBMS_STATS包来收集统计信息,因为DBMS_STATS包收集的更广,并且更准确。analyze 在以后的版本中可能会被移除。

DBMS_STATS常用的几个过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
dbms_stats.gather_table_stats 收集表、列和索引的统计信息;
dbms_stats.gather_schema_stats 收集SCHEMA下所有对象的统计信息;
dbms_stats.gather_index_stats 收集索引的统计信息;
dbms_stats.gather_system_stats 收集系统统计信息
dbms_stats.GATHER_DICTIONARY_STATS:所有字典对象的统计;
DBMS_STATS.GATHER_DICTIONARY_STATS 其收集所有系统模式的统计
dbms_stats.delete_table_stats 删除表的统计信息
dbms_stats.delete_index_stats 删除索引的统计信息
dbms_stats.export_table_stats 输出表的统计信息
dbms_stats.create_state_table
dbms_stats.set_table_stats 设置表的统计
dbms_stats.auto_sample_size

analyze 命令的语法如下:

1
2
3
SQL>analyze table tablename compute statistics;
SQL>analyze table tablename compute statistics for all indexes;
SQL>analyze table tablename delete statistics
2.3 统计信息的分类

Oracle 的Statistic 信息的收集分两种:自动收集和手工收集。

Oracle 的Automatic Statistics Gathering 是通过Scheduler 来实现收集和维护的。Job 名称是GATHER_STATS_JOB, 该Job收集数据库所有对象的2种统计信息:

(1)Missing statistics(统计信息缺失)

(2)Stale statistics(统计信息陈旧)

该Job 是在数据库创建的时候自动创建,并由Scheduler来管理。Scheduler 在maintenance windows open时运行gather job。 默认情况下,job 会在每天晚上10到早上6点和周末全天开启。该过程首先检测统计信息缺失和陈旧的对象。然后确定优先级,再开始进行统计信息。

Scheduler Job的stop_on_window_close 属性控制GATHER_STATS_JOB 是否继续。该属性默认值为True. 如果该值设置为False,那么GATHER_STATS_JOB 会中断, 而没有收集完的对象将在下次启动时继续收集。

Gather_stats_job 调用dbms_stats.gather_database_stats_job_proc过程来收集statistics 的信息。 该过程收集对象statistics的条件如下:

(1)对象的统计信息之前没有收集过。

(2)当对象有超过10%的rows 被修改,此时对象的统计信息也称为stale statistics。

但是对于高度变化的表在白天的活动期间被TRUNCATE/DROP并重建或者块加载超过本身总大小10%的对象;我们可以将这些表上的统计设置为NULL

可以通过以下SQL来查看:

1
2
3
select job_name, program_name, enabled, stop_on_window_close
  from dba_scheduler_jobs
 where job_name = 'gather_stats_job';

为了决定是否对对象进行监控,Oracle 提供了一个参数STATISTICS_LEVEL。通过设置初始化参数STATISTIC_LEVEL 为TYPICAL 或ALL,就可以自动收集统计信息(默认值为TYPICAL,因此可以随即启用自动收集统计信息的功能)。STATISTIC_LEVEL 参数的值可以激活GATHER_STATS_JOB。

在10g中表监控默认是激活的,如果STATISTICS_LEVEL设置为basic,不仅不能监控表,而且将禁掉如下一些10g的新功能:

(1)ASH(Active Session History)

(2)ASSM(Automatic Shared Memory Management)

(3)AWR(Automatic Workload Repository)

(4)ADDM(Automatic Database Diagnostic Monitor)

1
2
3
4
5
sys@ORCL> show parameter statistics_level;
       
NAME                                 TYPE        VALUE
------------------------------------ ----------- ---------
statistics_level                     string      TYPICAL

当启动对象的监控后,从上次统计信息收集之后的的信息,如inserts,updates,deletes 等,这些改变的信息会记录到user_tab_modifications 视图。

当对象的数据发生改变之后, 经过几分钟的延时,这些信息写入到user_tab_modifications视图,然后dbms_stats.flush_database_monitoring_info过程就会发现这些信息,并讲这些信息保存在内存中。

当监控的对象被修改的部分超过10%时,gather_database_stats 或者gather_schema_stats 过程就会去收集这些stale statistics

3.统计信息的存储位置以及常用数据字典

3.1 统计信息常用数据字典

统计信息收集如下数据:

(1)表自身的分析: 包括表中的行数,数据块数,行长等信息。

(2)列的分析:包括列值的重复数,列上的空值,数据在列上的分布情况。

(3)索引的分析: 包括索引叶块的数量,索引的深度,索引的聚合因子等。

这些统计信息存放在以下的数据字典里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DBA_TABLES
DBA_OBJECT_TABLES
DBA_TAB_STATISTICS
DBA_TAB_COL_STATISTICS
DBA_TAB_HISTOGRAMS
DBA_INDEXES
DBA_IND_STATISTICS
DBA_CLUSTERS
DBA_TAB_PARTITIONS
DBA_TAB_SUBPARTITIONS
DBA_IND_PARTITIONS
DBA_IND_SUBPARTITIONS
DBA_PART_COL_STATISTICS
DBA_PART_HISTOGRAMS
DBA_SUBPART_COL_STATISTICS
DBA_SUBPART_HISTOGRAMS
3.2 表的统计信息

包含表行数,使用的块数,空的块数,块的使用率,行迁移和链接的数量,pctfree,pctused的数据,行的平均大小:

1
2
3
4
5
6
7
SELECT NUM_ROWS, --表中的记录数
BLOCKS, --表中数据所占的数据块数
EMPTY_BLOCKS, --表中的空块数
AVG_SPACE, --数据块中平均的使用空间
CHAIN_CNT, --表中行连接和行迁移的数量
AVG_ROW_LEN --每条记录的平均长度
FROM USER_TABLES
3.3索引列的统计信息

包含索引的深度(B-Tree的级别),索引叶级的块数量,集群因子(clustering_factor), 唯一值的个数。

1
2
3
4
5
6
7
SELECT BLEVEL, --索引的层数
LEAF_BLOCKS, --叶子结点的个数
DISTINCT_KEYS, --唯一值的个数
AVG_LEAF_BLOCKS_PER_KEY, --每个KEY的平均叶块个数 
AVG_DATA_BLOCKS_PER_KEY, --每个KEY的平均数据块个数
CLUSTERING_FACTOR --群集因子
FROM USER_INDEXES
3.4 列的统计信息

包含唯一的值个数,列最大小值,密度(选择率),数据分布(直方图信息),NUll值个数

1
2
3
4
5
6
7
8
SELECT NUM_DISTINCT, --唯一值的个数
LOW_VALUE, --列上的最小值
HIGH_VALUE, --列上的最大值
DENSITY, --选择率因子(密度)
NUM_NULLS, --空值的个数
NUM_BUCKETS, --直方图的BUCKET个数
HISTOGRAM --直方图的类型
FROM USER_TAB_COLUMNS
对于统计信息的搜集,谈谈个人的几点理解:
1.统计信息默认是存放在数据字典表中的,也只有数据字典中的统计信息,才会影响到CBO。
2.DBMS_STATS 提供的CREATE_STAT_TABLE 过程,只是生成一个用户自定义的特定格式的表,用来存放统计信息罢了,这个表中的统计信息是不会影响到统计信息的。
3.GATHER 系列过程中,如果指定stattab,statid,statown 参数(也可以不指定),则是搜集的统计信息除了更新到数据字典外,还在statown 用户下的stattab 表中存放一份,标示为 statid;
4.EXPORT和IMPORT 系列的过程中,stattab,statid,statown 参数不能为空,分别表示把数据字典中的当前统计信息导出到用户自定义的表中,以及把用户表中的统计信息导入到数据字典中,很明显可以看出,这里的导入操作和上面GATHER 操作会改变统计信息,可能会引起执行执行计划的改变,因此要慎重操作。
5.每次统计信息搜集前,将旧的统计信息备份起来是很有必要的;特别是保留一份或多份系统在稳定时期的统计信息也是很有必要的。
6.多长时间搜集一次统计信息,对于统计信息如何备份和保留,搜集统计信息时如何选择合适的采样,并行,直方图设置等都比较重要,需要设计一个较好的统计信息搜集策略。

在OCP 10g考试中会有个跟统计信息相关的考试题:

1. In your Oracle 10g database , you have scheduled a job to update the optimizer statistics at 05:00 pm

every Friday. The job has successfully completed. Which three pieces of information would you check to

confirm that the statistics have been collected? (Choose three.)

A. average row size

B. last analyzed date

C. size of table in bytes

D. size of table in database blocks

E. number of free blocks in the free list

F. number of extents present in the table

Answer: ABD

posted @ 2014-05-16 19:08 鸿雁 阅读(276) | 评论 (0)编辑 收藏

Oracle统计信息的收集、管理与清除

     摘要: 以下测试环境为Oracle 10g 10.2.0.4版本,测试对Oracle的统计信息的收集与管理。首先依据dba_objects创建一张测试表: SQL> create table eygle as select * from dba_objects; Table created对该表进行统计信息收集,这里未指定method_opt,则Oracle将采用 FOR ALL COL...  阅读全文

posted @ 2014-05-16 18:21 鸿雁 阅读(176) | 评论 (0)编辑 收藏

oracle sql日期相关

     摘要: 获取昨天: select trunc(SYSDATE-1)  from dual; 检查一下: select to_char (trunc(SYSDATE-1),'yyyy-mm-dd HH24:MI:SS') from dual; 获取上个月第一天00:00:00:  select add_months(trunc(sysdate,'MON'),-1) from d...  阅读全文

posted @ 2014-05-15 17:36 鸿雁 阅读(359) | 评论 (0)编辑 收藏

Oracle 数据库启动的不同状态

Oracle数据库启动分为三个步骤:

nomount,找到初始化文件pfile或者spfile,创建SGA并启动后台进程但不允许访问数据库。

mount,根据初始化文件找到控制文件(Control File),为某些DBA活动装载数据库但不允许用户访问数据库。

open,根据控制文件找到数据文件(Data File),重做日志文件(Redo File),使用户可以访问数据库。

 

关闭数据库的4个不同命令

shutdown normal(等于shutdown)

正常是关闭的缺省方式正常的数据库关闭在下列情况下进行

• 不允许新的连接

• 等待会话结束

• 等待事务结束

• 做一个检查点并关闭数据文件

• 下一次启动时将不要求实例恢复

shutdown transactional

事务处理关闭防止客户丢失工作事务处理数据库关闭在下列情况下进行

• 不允许新的连接

• 不等待会话结束

• 等待事务结束

• 做一个检查点并关闭数据文件

• 下一次启动将不要求实例恢复

shutdown immediate

立即关闭数据库在下列情况下进行

• 不允许新的连接

• 不等待会话结束

• 不等待事务结束

• 未结束的事务自动回滚(rollback)

• 做一个检查点并关闭数据文件

• 下一次启动将不要求例程恢复

shutdown abort

如果正常和立即关闭选项不起作用可以中止当前数据库例程中止例程可以在下列情况下进行

• 不允许新的连接

• 不等待会话结束

• 不等待事务结束

• 不做检查点且没有关闭数据文件

• 下一次启动将要求实例恢复

 

 

启动数据库相关命令

startup(默认启动到open状态)

startup nomount

startup mount

alter database mount;

alter database open;

 

初始化文件

pfile,文本文件,可以手工编辑该文件

spfile,二进制文件,不能手工修改里面的参数,只能使用数据库命令进行修改,数据库启动默认使用spfile

 

查看初始化文件路径

show parameter spfile;

 

根据spfile创建pfile文件

create pfile from spfile;

create pfile='/u01/app/pfile.ora' fromspfile;

create spfile from pfile='/u01/app/pfile.ora';

 

查看控制文件的路径

select name from v$controlfile;

 

查看控制文件的内容,可以根据ctl.trc重建控制文件

alter database backup controlfile to trace as '/u01/app/ctl.trc';

 

查看数据文件的位置

select name from v$datafile;

 

查看重做日志文件的位置

select member from v$logfile;

 

数据库的密码文件

Windows,$ORACLE_HOME/database/PWDorcl.ora

Linux/Unix,$ORACLE_HOME/dbs/orapworcl

密码文件的作用是允许数据库的sysdba、sysoper用户通过口令来远登陆

 

查看数据库是否允许用户远程登陆数据库

show parameter remote_login_passwordfile;

如果remote_login_passwordfile为EXCLUSIVE则表示允许远程连接数据库

如果remote_login_passwordfile为NONE则表示不允许远程连接数据库

 

禁止用户远程登陆数据库(需要重启数据库生效)

alter system set remote_login_passwordfile=none scope=spfile;

 

表空间

select * from dba_tablespaces;

 

数据文件

select * from dba_data_files;

 

重做日志文件

select * from v$logfile;

 

重做日志文件的状态

STALE表示数据已经提交到数据库中,空白状态表示正在使用该文件

 

posted @ 2014-05-11 19:54 鸿雁 阅读(134) | 评论 (0)编辑 收藏

如何查看及解决最耗cpu的sql语句

1.      如何查看及解决最耗CPUSQL语句1.1.  top监控服务器负载

[root@node1 ~]# top

top - 22:51:02 up 56 min,  1 user,  load average: 0.00, 0.00, 0.00

Tasks:  96 total,   1 running,  95 sleeping,   0 stopped,   0 zombie

Cpu(s):  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st

Mem:   1035096k total,   351488k used,   683608k free,    24140k buffers

Swap:  2096472k total,        0k used,  2096472k free,   270360k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND               

    1 root      15   0  2084  660  560 S  0.0  0.1   0:00.22 init                    

    2 root      RT  -5     0    0    0 S  0.0  0.0   0:00.00 migration/0   

         如果发现user中的CPU过高,比如oracle中最高的进程pid1138782,占CPU27%,则执行下一步。

1.2.  查询数据库会话的sidserial#

进入数据库,根据oracle进程的pid查出对应数据库会话的sidserial#

select s.sid,s.serial#

from v$session s,v$process p

where s.paddr=p.addr and p.spid='1138782';

         查询出来的结果sidserial#分别为48256767

1.3.  查询SQL语句

根据数据库会话的sid查出具体的SQL语句:

desc v$sqltext

名称                                      是否为空? 类型

----------------------------------------- -------- --------------

ADDRESS                                            RAW(4)

HASH_VALUE                                         NUMBER

COMMAND_TYPE                                      NUMBER

PIECE                                               NUMBER

SQL_TEXT                                           VARCHAR2(64)

select sql_text

from v$sqltext a

where a.hash_value=(select sql_hash_value from v$session b

                   where b.sid='&sid')

order by piece;

输入 sid 的值:

1.4.  处理SQL语句

如果SQL语句影响了数据库的运行,可以killSQL语句的会话:

①在数据库中杀死SQL语句的会话:

alter system kill session '482,56767';

         如果不能在数据库中杀死SQL语句,可在LINUX系统中强制杀死Oracle进程

         ②在linux系统中强制杀死oracle进程

#kill -9 1138782

1.5.  SQL语句优化

最后可以根据步骤(3)查询出来的SQL语句进行优化,以避免再次出现上述消耗CPU的情况。

posted @ 2014-05-11 19:44 鸿雁 阅读(198) | 评论 (0)编辑 收藏

Oracle 分区表的新增、修改、删除、合并。普通表转分区表方法

     摘要: 一. 分区表理论知识 Oracle提供了分区技术以支持VLDB(Very Large DataBase)。分区表通过对分区列的判断,把分区列不同的记录,放到不同的分区中。分区完全对应用透明。 Oracle的分区表可以包括多个分区,每个分区都是一个独立的段(SEGMENT),可以存放到不同的表空间中。查询时可以通过查询表来访问各个分区中的数据,也可以通过在查询时直接指定分区的方法来进行查询。 ...  阅读全文

posted @ 2014-05-10 12:19 鸿雁 阅读(7780) | 评论 (0)编辑 收藏

Oracle分区表

1.分区表的维护注意事项
  1. 若分区表跨不同表空间,做导出、导入时目标数据库必须预建这些表空间。分表区各区所在表空间在做导入时目标数据库一定要预建这些表空间!这些表空间不一定是用户的默认表空间,只要存在即可。如果有一个不存在,就会报错!
  2. 默认时,对分区表的许多表维护操作会使全局索引不可用,标记成UNUSABLE。 那么就必须重建整个全局索引或其全部分区。如果已被分区,Oracle 允许在用于维护操作的ALTER TABLE 语句中指定UPDATE GLOBAL INDEXES 来重载这个默认特性,指定这个子句也就告诉Oracle 当它执行维护操作的DDL 语句时更新全局索引,这提供了如下好处:
    1. 在操作基础表的同时更新全局索引这就不需要后来单独地重建全局索引;
    2. 因为没有被标记成UNUSABLE, 所以全局索引的可用性更高了,甚至正在执行分区的DDL 语句时仍然可用索引来访问表中的其他分区,避免了查询所有失效的全局索引的名字以便重建它们;

另外在指定UPDATE GLOBAL INDEXES 之前还要考虑如下性能因素:

  1. 因为要更新事先被标记成UNUSABLE 的索引,所以分区的DDL 语句要执行更长时间,当然这要与先不更新索引而执行DDL 然后再重建索引所花的时间做个比较,一个适用的规则是如果分区的大小小于表的大小的5% ,则更新索引更快一点;
  2. DROP TRUNCATE 和EXCHANGE 操作也不那么快了,同样这必须与先执行DDL 然后再重建所有全局索引所花的时间做个比较;
  3. 要登记对索引的更新并产生重做记录和撤消记录,重建整个索引时可选择NOLOGGING;
  4. 重建整个索引产生一个更有效的索引,因为这更利于使用空间,再者重建索引时允许修改存储选项;
  5. 分区索引结构表不支持UPDATE GLOBAL INDEXES 子句。
2.普通表变为分区表

将已存在数据的普通表转变为分区表,没有办法通过修改属性的方式直接转化为分区表,必须通过重建的方式进行转变,一般可以有三种方法,视不同场景使用:

2.1方法一:利用原表重建分区表。

CREATE TABLE T (ID NUMBER PRIMARY KEY, TIME DATE); 

INSERT INTO t
  SELECT Rownum, SYSDATE - Rownum FROM Dba_Objects WHERE Rownum <= 5000;
COMMIT;

CREATE TABLE T_NEW (ID, TIME) PARTITION BY RANGE (TIME) 
 (PARTITION P1 VALUES LESS THAN (TO_DATE('2000-1-1', 'YYYY-MM-DD')), 
 PARTITION P2 VALUES LESS THAN (TO_DATE('2002-1-1', 'YYYY-MM-DD')), 
 PARTITION P3 VALUES LESS THAN (TO_DATE('2005-1-1', 'YYYY-MM-DD')), 
 PARTITION P4 VALUES LESS THAN (MAXVALUE))
 AS SELECT ID, TIME FROM T; 

RENAME T TO T_OLD; 

RENAME T_NEW TO T; 

 

 

优点:方法简单易用,由于采用DDL语句,不会产生UNDO,且只产生少量REDO,效率相对较高,而且建表完成后数据已经在分布到各个分区中了。

不足:对于数据的一致性方面还需要额外的考虑。由于几乎没有办法通过手工锁定T表的方式保证一致性,在执行CREATE TABLE语句和RENAME T_NEW TO T语句直接的修改可能会丢失,如果要保证一致性,需要在执行完语句后对数据进行检查,而这个代价是比较大的。另外在执行两个RENAME语句之间执行的对T的访问会失败。

适用性:适用于修改不频繁的表,在闲时进行操作,表的数据量不宜太大。

2.2方法二:使用交换分区的方法

Drop table t;
CREATE TABLE T (ID NUMBER PRIMARY KEY, TIME DATE); 
INSERT INTO t
  SELECT Rownum, SYSDATE - Rownum FROM Dba_Objects WHERE Rownum <= 5000;
COMMIT;

CREATE TABLE T_NEW (ID NUMBER PRIMARY KEY, TIME DATE) PARTITION BY RANGE (TIME) 
 (PARTITION P1 VALUES LESS THAN (TO_DATE('2005-9-1', 'YYYY-MM-DD')), 
 PARTITION P2 VALUES LESS THAN (MAXVALUE)); 

ALTER TABLE T_NEW EXCHANGE PARTITION P1 WITH TABLE T; 

RENAME T TO T_OLD; 

RENAME T_NEW TO T; 

 

 

优点:只是对数据字典中分区和表的定义进行了修改,没有数据的修改或复制,效率最高。如果对数据在分区中的分布没有进一步要求的话,实现比较简单。在执行完RENAME操作后,可以检查T_OLD中是否存在数据,如果存在的话,直接将这些数据插入到T中,可以保证对T插入的操作不会丢失。

不足:仍然存在一致性问题,交换分区之后RENAME T_NEW TO T之前,查询、更新和删除会出现错误或访问不到数据。如果要求数据分布到多个分区中,则需要进行分区的SPLIT操作,会增加操作的复杂度,效率也会降低。

适用性:适用于包含大数据量的表转到分区表中的一个分区的操作。应尽量在闲时进行操作。

2.3方法三:Oracle9i以上版本,利用在线重定义功能

Drop table t;
CREATE TABLE T (ID NUMBER PRIMARY KEY, TIME DATE); 
INSERT INTO T
 SELECT ROWNUM, SYSDATE - ROWNUM FROM DBA_OBJECTS WHERE ROWNUM <= 5000;
COMMIT;

EXEC DBMS_REDEFINITION.CAN_REDEF_TABLE(USER, 'T'); 

PL/SQL 过程已成功完成。 

CREATE TABLE T_NEW (ID NUMBER PRIMARY KEY, TIME DATE) PARTITION BY RANGE (TIME) 
 (PARTITION P1 VALUES LESS THAN (TO_DATE('2004-7-1', 'YYYY-MM-DD')), 
 PARTITION P2 VALUES LESS THAN (TO_DATE('2005-1-1', 'YYYY-MM-DD')), 
 PARTITION P3 VALUES LESS THAN (TO_DATE('2005-7-1', 'YYYY-MM-DD')), 
 PARTITION P4 VALUES LESS THAN (MAXVALUE)); 

表已创建。 

EXEC DBMS_REDEFINITION.START_REDEF_TABLE(USER, 'T', 'T_NEW'); 

PL/SQL 过程已成功完成。 

EXEC DBMS_REDEFINITION.FINISH_REDEF_TABLE(USER, 'T', 'T_NEW'); 

PL/SQL 过程已成功完成。 

 

 

优点:保证数据的一致性,在大部分时间内,表T都可以正常进行DML操作。只在切换的瞬间锁表,具有很高的可用性。这种方法具有很强的灵活性,对各种不同的需要都能满足。而且,可以在切换前进行相应的授权并建立各种约束,可以做到切换完成后不再需要任何额外的管理操作。

不足:实现上比上面两种略显复杂。

适用性:适用于各种情况。

这里只给出了在线重定义表的一个最简单的例子,详细的描述和例子可以参考下面两篇文章。

Oracle的在线重定义表功能:http://blog.itpub.net/post/468/12855

Oracle的在线重定义表功能(二):http://blog.itpub.net/post/468/12962

2.4把一个已存在数据的大表改成分区表:

第一种(表不是太大):

--1.把原表改名:
rename xsb1 to xsb2;
--2.创建分区表:
CREATE TABLE xsb1
 PARTITION BY LIST (c_test) 
 (PARTITION xsb1_p1 VALUES (1),
 PARTITION xsb1_p2 VALUES (2),
 PARTITION xsb1_p0 VALUES (default))
 nologging AS SELECT * FROM xsb2;
--3.将原表上的触发器、主键、索引等应用到分区表上;
--4.删除原表:
drop table xsb2;

 

 

第二种(表很大):

--1. 创建分区表:
CREATE TABLE x PARTITION BY LIST (c_test) [range ()]
 (PARTITION p0 VALUES [less than ](1) tablespace tbs1,
 PARTITION p2 VALUES (2) tablespace tbs1,
 PARTITION xsb1_p0 VALUES ([maxvalue]default))
 AS SELECT * FROM xsb2 [where 1=2];
--2. 交换分区 
alter table x exchange partition p0 with table bsvcbusrundatald ;
--3. 原表改名
alter table bsvcbusrundatald rename to x0;
--4. 新表改名
alter table x rename to bsvcbusrundatald ;
--5. 删除原表
drop table x0;
--6. 创建新表触发器和索引
create index ind_busrundata_lp on bsvcbusrundatald(。。。) local tablespace tbs_brd_ind ;

 

 

或者:

1. 规划原大表中数据分区的界限,原则上将原表中近期少量数据复制至另一表;

2. 暂停原大表中的相关触发器;

3. 删除原大表中近期数据;

4. 改名原大表名称;

5. 创建分区表;

6. 交换分区;

7. 重建相关索引及触发器(先删除之再重建).

参考脚本:

select count(*) from t1 where recdate>sysdate-2;

create table x2 nologging as select * from t1 where recdate>trunc(sysdate-2);

alter triger trg_t1 disable;

delete t1 where recdate>sysdate-2;

commit;

rename t1 to x1;

create table t1 [nologging] partition by range(recdate)

(partition pbefore values less than (trunc(sysdate-2)),

partition pmax values less than (maxvalue))

as select * from x1 where 1=2;

alter table t1 exchange partition pbefore with table x1;

alter table t1 exchange partition pmax with table x2;

drop table x2;

--重建触发器

drop table x1;

 

 

3.分区的方法:
  1. 范围分区Range
  2. 散列分区Hash
  3. 列表分区List
  4. 组合范围-散列分区Range-Hash
  5. 组合范围-列表分区Range-List

可对索引和表分区。全局索引只能按范围分区,但可以将其定义在任何类型的分区或非分区表上。通常全局索引比局部索引需要更多的维护。

一般组建局部索引,以便反映其基础表的结构。它与基础表是等同分区的,即它与基础表在同样的列上分区,创建同样数量的分区或子分区,设置与基础表相对应的同样的分区边界。对局部索引而言,当维护活动影响分区时,会自动维护索引分区。这保证了索引与基础表之间的等同分区。

关于范围分区Range:

要想将行映射到基于列值范围的分区,就使用范围分区方法。当数据可以被划分成逻辑范围时如年度中的月份,这种类型的分区就有用了。当数据在整个范围中能被均等地划分时性能最好。如果靠范围的分区会由于不均等的划分而导致分区在大小上明显不同时,就需要考虑其他的分区方法。

关于散列分区Hash:

如果数据不那么容易进行范围分区,但为了性能和管理的原因又想分区时,就使用散列分区方法。散列分区提供了一种在指定数量的分区中均等地划分数据的方法。基于分区键的散列值将行映射到分区中。创建和使用散列分区会给你提供了一种很灵活的放置数据的方法,因为你可以通过在I/O 驱动器之间播撒(摘掉)这些均等定量的分区,来影响可用性和性能。

关于列表分区List:

当你需要明确地控制如何将行映射到分区时,就使用列表分区方法。可以在每个分区的描述中为该分区列指定一列离散值,这不同于范围分区,在那里一个范围与一个分区相关,这也不同于散列分区,在那里用户不能控制如何将行映射到分区。列表分区方法是特意为遵从离散值的模块化数据划分而设计的。范围分区或散列分区不那么容易做到这一点。进一步说列表分区可以非常自然地将无序的和不相关的数据集进行分组和组织到一起。

与范围分区和散列分区所不同,列表分区不支持多列分区。如果要将表按列分区,那么分区键就只能由表的一个单独的列组成,然而可以用范围分区或散列分区方法进行分区的所有的列,都可以用列表分区方法进行分区。

关于组合范围-散列分区:

范围和散列技术的组合,首先对表进行范围分区,然后用散列技术对每个范围分区再次分区。给定的范围分区的所有子分区加在一起表示数据的逻辑子集。

关于组合范围-列表分区:

范围和列表技术的组合,首先对表进行范围分区,然后用列表技术对每个范围分区再次分区。与组合范围-散列分区不同的是,每个子分区的所有内容表示数据的逻辑子集,由适当的范围和列表分区设置来描述。

注意:创建或更改分区表时可以指定行移动子句,即ENABLE ROW MOVEMENT 或者DISABLE ROW MOVEMENT ,当其键被更改时,该子句启用或停用将行迁移到一个新的分区。默认值为DISABLE ROW MOVEMENT。

如果表中预期的数据量较大,通常都需要考虑使用分区表,确定使用分区表后,还要确定什么类型的分区(range partition、hash partition、list partition等)、分区区间大小等。分区的创建最好与程序有某种默契,比如创建一个分区表,按自然月份定义分区的,但程序却在查询时默认的开始时间与结束时间是:当前日期-30至当前日期,比如当天是9.18号,那查询条件被产生为8.18-9.18,结果分区后并不没有大幅提高性能。为此将程序的查询日期做了调整,按自然月查询,系统的负载小了很多。

4.补充资料

从Oracle8.0开始支持表分区(MSSQL2005开始支持表分区)。

Oracle9i 分区能够提高许多应用程序的可管理性、性能与可用性。分区可以将表、索引及索引编排表进一步划分,从而可以更精细地对这些数据库对象进行管理和访问。Oracle 提供了种类繁多的分区方案以满足所有的业务需要。另外,由于在 SQL 语句中是完全透明的,所以分区可以用于几乎所有的应用程序。

分区表允许将数据分成被称为分区甚至子分区的更小的更好管理的块。索引也可以这么分区。每个分区可以被单独管理,可以不依赖于其他分区而单独发挥作用,因此提供了一个更有利于可用性和性能的结构。

分区可以提高可管理性、性能与可用性,从而给各种各样的应用程序带来极大的好处。通常,分区可以使某些查询以及维护操作的性能大大提高。此外,分区还能够在很大程度上简化日常管理任务。分区还使数据库设计人员和管理员能够解决尖端应用程序带来的最难的问题。分区是建立上亿万字节数据系统或需要极高可用性系统的关键工具。

在多CPU配置环境下,如果打算使用并行执行,则分区提供了另一种并行的方法。通过给表或索引的不同分区分配不同的并行执行服务器,就可以并行执行对分区表和分区索引的操作。

表或索引的分区和子分区都共享相同的逻辑属性。例如表的所有分区或子分区共享相同的列和约束定义,一个索引的分区或子分区共享相同的索引选项。然而它们可以具有不同的物理属性如表空间。

尽管不需要将表或索引的每个分区或子分区放在不同的表空间,但这样做更好。将分区存储到不同的表空间能够:

  1. 减少数据在多个分区中冲突的可能性
  2. 可以单独备份和恢复每个分区
  3. 控制分区与磁盘驱动器之间的映射对平衡I/O 负载是重要的
  4. 改善可管理性可用性和性能

分区操作对现存的应用和运行在分区表上的标准DML 语句来说是透明的。但是可以通过在DML 中使用分区扩展表或索引的名字来对应用编程,使其利用分区的优点。

可以使用SQL*Loader、Import 和Export 工具来装载或卸载分区表中的数据。这些工具都是支持分区和子分区的。

4.1分区技术能够提高数据库的可管理性:

使用分区技术,维护操作可集中于表的特定部分。例如,数据库管理员可以只对表的一部分做备份,而不必对整个表做备份。对整个数据库对象的维护操作,可以在每个分区的基础上进行,从而将维护工作分解成更容易管理的小块。

分区技术提高可管理性的一个典型用法是支持数据仓库中的‘滚动视窗’加载进程。假设数据库管理员每周向表中加载新数据。该表可以是范围分区,以便每个分区包含一周的数据。加载进程只是简单地添加新的分区。添加一个新分区的操作比修改整个表效率高很多,因为数据库管理员不需要修改任何其他分区。从分区后的表中去除数据也是一样。你只要用一个很简便快捷的数据字典操作删掉一个分区,而不必发出使用大量资源和调动所有要删除的数据的 ‘DELETE’ 命令。

4.2分区技术能够提高数据库的性能:

由于减少了所检查或操作的数据数量,同时允许并行执行,Oracle9i 的分区功能提供了性能上的优势。这些性能包括:

  1. 分区修整:分区修整是用分区技术提高性能的最简单最有价值的手段。分区修整常常能够将查询性能提高几个数量级。例如,假定应用程序中有包含定单历史记录的定单表,该表用周进行了分区。查询一周的定单只需访问该定单表的一个分区。如果该定单表包含两年的历史记录,这个查询只需要访问一个而不是一百零四个分区。该查询的执行速度因为分区修整而有可能快一百倍。分区修整能与所有其他 Oracle 性能特性协作。Oracle 公司将把分区修整技术与索引技术、连结技术和并行访问方法一起联合使用。
  2. 分区智能联接:分区功能可以通过称为分区智能联接的技术提高多表联接的性能。当两个表要联接在一起,而且每个表都用联接关键字来分区时,就可以使用分区智能联接。分区智能联接将大型联接分解成较小的发生在各个分区间的联接,从而用较少的时间完成全部联接。这就给串行和并行的执行都能带来显著的性能改善。
  3. 更新和删除的并行执行:分区功能能够无限地并行执行 UPDATE、DELETE 与 MERGE 语句。当访问分区或未分区的数据库对象时Oracle 将并行处理 SELECT 与 INSERT 语句。当不使用位图索引时,也可以对分区或未分区的数据库对象并行处理 UPDATE、DELETE 和 MERGE 语句。为了对有位图索引的对象并行处理那些操作,目标表必须先分区。这些 SQL 语句的并行执行可以大大提高性能,特别是提高 UPDATE 与 DELETE 或 MERGE 操作涉及大量数据时的性能。
4.3分区技术提高可用性:

分区的数据库对象具有分区独立性。该分区独立性特点可能是高可用性战略的一个重要部分,例如,如果分区表的分区不能用,但该表的所有其他分区仍然保持在线并可用。那么这个应用程序可以继续针对该分区表执行查询和事务处理,只要不是访问那个不可用的分区,数据库操作仍然能够成功运行。 数据库管理员可以指定各分区存放在不同的表空间里,从而让管理员独立于其它表分区针对每个分区进行备份与恢复操作。 还有,分区功能可以减少计划停机时间。性能由于分区功能得到了改善,使数据库管理员在相对较小的批处理窗口完成大型数据库对象的维护工作。

posted @ 2014-05-07 22:53 鸿雁 阅读(200) | 评论 (0)编辑 收藏

普通表转分区表和交换分区(oracle)

将普通表转换成分区表有4种方法:  

       1. Export/import method  

       2. Insert with a subquery method  

       3. Partition exchange method  

       4.
DBMS_REDEFINITION           


    select
 * from t_user_info_test;  


    --方法一
  

    drop table t_phone_test purge;  

create table t_phone_test(phone,part) nologging  partition by list(part)  

(  

partition p0 values('0'),  

partition p1 values('1'),  

partition p2 values('2'),  

partition p3 values('3'),  

partition p4 values('4'),  

partition p5 values('5'),  

partition p6 values('6'),  

partition p7 values('7'),  

partition p8 values('8'),  

partition p9 values('9')  

)   

as   

select user_mobile phone,substr(user_mobile,-1,1) part  

from t_user_info_test;  

  

  

select * from t_phone_test partition(p0);  

  

select * from t_phone_test where part='0';  

  

--方法二 交换分区   

     这种方法只是对数据字典中分区和表的定义进行了修改,没有数据的修改或复制,效率最高。适用于包含大数据量的表转到分区表中的一个分区的操作。尽量在闲时进行操作。  

  

交换分区的操作步骤如下:  

     1. 创建分区表,假设有2个分区,P1,P2.  

     2. 创建表A存放P1规则的数据。  

     3. 创建表B 存放P2规则的数据。  

     4. 用表A 和P1 分区交换。 把表A的数据放到到P1分区  

     5. 用表B 和p2 分区交换。 把表B的数据存放到P2分区。  

  

  

  

create table t_phone_test_0 nologging  

as   

select user_mobile phone,substr(user_mobile,-1,1) part  

from t_user_info_test where substr(user_mobile,-1,1)='0';  

  

select count(*) from t_phone_test where part='0';  

--4410   

select count(*) from t_user_info_test where substr(user_mobile,-1,1)='0';  

--4410   

  

alter table t_phone_test exchange partition p0 with table t_phone_test_0;  

  

  

delete from   t_phone_test_0;  

  

select count(*) from t_phone_test where part='0';  

select count(*) from t_phone_test_0;  

  

insert into t_phone_test(phone,part) values('15267046070','0');  

  

--p0一条数据,t_phone_test_0里4410条数据,交换之后p0是4410,t_phone_test_0是1,再执行一次数据又换回来了。   

  

insert into t_phone_test_0(phone,part) values('15267046070','1');  

alter table t_phone_test exchange partition p0 with table t_phone_test_0;  

delete from t_phone_test_0 where part='1';  

  

  

--合并分区   

----alter table tbname merge partitions/subpartitions pt1,pt2 into partition/subpartition pt3;   

  

alter table t_phone_test merge partitions p0,p1 into partition p0;  

  

  

select count(*) from t_phone_test where part='0';  

select count(*) from t_phone_test where part='1';  

  

select count(*)  from t_phone_test partition(p0);  

select count(*)  from t_phone_test partition(p1);  

  

  

  

 alter table t_phone_test  add partition p10 values(default);  

  

insert into t_phone_test(phone,part) values('15267046010','10');  

insert into t_phone_test(phone,part) values('15267046020','20');  

  

select * from   

  

--   

alter table t_phone_test drop partition p10;  

 alter table t_phone_test  add partition p10 values'10');  

   

alter table t_phone_test exchange partition p10 with table t_phone_test_10;  

--ORA-14097: column type or size mismatch in ALTER TABLE EXCHANGE PARTITION   

alter table T_PHONE_TEST_10 modify PART VARCHAR2(2);  

alter table t_phone_test merge partitions p0,p10 into partition p0;  

  

--此时p0中有p0和p10的数据,但是p0的list不再是0而是0和10   

  partition P0 values ('10''0')  

    tablespace APP_DATAN  

    pctfree 10  

    initrans 1  

    maxtrans 255  

    storage  

    (  

      initial 1M  

      next 1M  

      minextents 1  

      maxextents unlimited  

      pctincrease 0  

    ),  

      

alter table t_phone_test exchange partition p0 with table t_phone_test_10;     

alter table t_phone_test drop partition p0;  

alter table t_phone_test  add partition p0 values'0');      

  

alter table t_phone_test exchange partition p0 with table t_phone_test_10;     

  

  

drop table t_phone_test_10 purge;  

create table t_phone_test_10 nologging  

as   

select user_mobile phone,substr(user_mobile,-2,2) part  

from t_user_info_test where substr(user_mobile,-2,2)='10';  

  

drop table t_phone_test_0 purge;  

create table t_phone_test_0 nologging   

as  

select  phone,substr(phone,-1,1) part  

from t_phone_test_10;  

  

alter table t_phone_test exchange partition p0 with table t_phone_test_0;  

  

  

select * from t_phone_test_10;  

  

  

  

select count(*)  from t_phone_test partition(p0);  

select count(*)  from t_phone_test partition(p10);  

select count(*) from t_phone_test_10;  

select count(*) from t_phone_test_0;  

  

  

  

select substr('123456',-1,1),substr('123456',-2,2),substr('123456',-3,2) from dual;  

  

  

---------------------------------------------------------   

1.创建分区表  

drop table t_phone_test purge;  

create table t_phone_test(phone,part) nologging  partition by list(part)  

(  

partition p0 values('0'),  

partition p1 values('1'),  

partition p2 values('2'),  

partition p3 values('3'),  

partition p4 values('4'),  

partition p5 values('5'),  

partition p6 values('6'),  

partition p7 values('7'),  

partition p8 values('8'),  

partition p9 values('9')  

)   

as   

select user_mobile phone,substr(user_mobile,-1,1) part  

from t_user_info_test;  

  

select count(*)  from t_phone_test partition(p0);--4410   

select count(*)  from t_phone_test partition(p10);  

select count(*) from t_phone_test_10;  

select count(*) from t_phone_test_0;  

  

2.创建基表  

drop table t_phone_test_10 purge;  

create table t_phone_test_10 nologging  

as  

select  phone,substr(phone,-2,2) part  

from t_phone_test where substr(phone,-2,2)='10';  

  

select count(*) from t_phone_test_10;--406   

  

--ORA-14097: column type or size mismatch in ALTER TABLE EXCHANGE PARTITION   

alter table T_PHONE_TEST_10 modify PART VARCHAR2(2);  

  

3.添加分区  

alter table t_phone_test  add partition p10 values'10');      

select count(*)  from t_phone_test partition(p10);--0   

4.交换分区  

alter table t_phone_test exchange partition p10 with table t_phone_test_10;     

select count(*)  from t_phone_test partition(p10);--406   

5.合并分区  

alter table t_phone_test merge partitions p0,p10 into partition p0;  

select count(*)  from t_phone_test partition(p0);--4816   

--此时p0中有p0和p10的数据,但是p0的list不再是0而是0和10   

  partition P0 values ('10''0')  

    tablespace APP_DATAN  

    pctfree 10  

    initrans 1  

    maxtrans 255  

    storage  

    (  

      initial 1M  

      next 1M  

      minextents 1  

      maxextents unlimited  

      pctincrease 0  

    ),  

      

6.交换分区  

alter table t_phone_test exchange partition p0 with table t_phone_test_10;    

  

select count(*)  from t_phone_test partition(p0);--0   

select count(*) from t_phone_test_10;--4816   

  

  

6.删除分区 和添加分区  

alter table t_phone_test  drop partition p0;  

alter table t_phone_test  add partition p0 values('0');  

  

7.筛选数据  

drop table t_phone_test_0 purge;  

create table t_phone_test_0 nologging  

as  

select  phone,substr(phone,-1,1) part  

from t_phone_test_10 where substr(phone,-1,1)='0';  

  

select count(*) from t_phone_test_0;--4816   

  

8.交换分区  

alter table t_phone_test exchange partition p0 with table t_phone_test_0;    

  

select count(*)  from t_phone_test partition(p0);--4816   
select count(*) from t_phone_test_0;--0  

posted @ 2014-05-07 22:31 鸿雁 阅读(302) | 评论 (0)编辑 收藏

Oracle全局索引和本地索引

      Oracle数据库中,有两种类型的分区索引,全局索引和本地索引,其中本地索引又可以分为本地前缀索引和本地非前缀索引。下面就分别看看每种类型的索引各自的特点。

全局索引以整个表的数据为对象建立索引,索引分区中的索引条目既可能是基于相同的键值但是来自不同的分区,也可能是多个不同键值的组合。

全局索引既允许索引分区的键值和表分区键值相同,也可以不相同。全局索引和表之间没有直接的联系,这一点和本地索引不同。

SQL> create table orders (
     order_no      number,
     part_no       varchar2(40),
     ord_date      date
     )
     partition by range (ord_date)
      (partition Q1 values less than (TO_DATE('01-APR-1999','DD-MON-YYYY')),
       partition Q2 values less than (TO_DATE('01-JUL-1999','DD-MON-YYYY')),
       partition Q3 values less than (TO_DATE('01-OCT-1999','DD-MON-YYYY')),
       partition Q4 values less than (TO_DATE('01-JAN-2000','DD-MON-YYYY'))
      )
     ;

Table created.

SQL> create index orders_global_1_idx
     on orders(ord_date)
      global partition by range (ord_date)
       (partition GLOBAL1 values less than (TO_DATE('01-APR-1999','DD-MON-YYYY')),
        partition GLOBAL2 values less than (TO_DATE('01-JUL-1999','DD-MON-YYYY')),
        partition GLOBAL3 values less than (TO_DATE('01-OCT-1999','DD-MON-YYYY')),
        partition GLOBAL4 values less than (MAXVALUE)
       )
     ;

Index created.

SQL> create index orders_global_2_idx
     on orders(part_no)
      global partition by range (part_no)
       (partition IND1 values less than (555555),
        partition IND2 values less than (MAXVALUE)
       )
     ;

Index created.

从上面的语句可以看出,全局索引和表没有直接的关联,必须显式的指定maxvalue值。假如表中新加了分区,不会在全局索引中自动增加新的分区,必须手工添加相应的分区。

SQL> alter table orders add partition Q5 values less than (TO_DATE('01-APR-2000','DD-MON-YYYY'));

Table altered.

SQL> select TABLE_NAME, PARTITION_NAME from dba_tab_partitions where table_name='ORDERS';

TABLE_NAME                     PARTITION_NAME
------------------------------ ------------------------------
ORDERS                         Q1
ORDERS                         Q2
ORDERS                         Q3
ORDERS                         Q4
ORDERS                         Q5

SQL> select INDEX_NAME, PARTITION_NAME from dba_ind_partitions where index_name=upper('orders_global_1_idx');

INDEX_NAME                     PARTITION_NAME
------------------------------ ------------------------------
ORDERS_GLOBAL_1_IDX            GLOBAL1
ORDERS_GLOBAL_1_IDX            GLOBAL2
ORDERS_GLOBAL_1_IDX            GLOBAL3
ORDERS_GLOBAL_1_IDX            GLOBAL4

使用全局索引,索引键值必须和分区键值相同,这就是所谓的前缀索引。Oracle不支持非前缀的全局分区索引,如果需要建立非前缀分区索引,索引必须建成本地索引。

SQL> create index orders_global_2_idx
  2  on orders(part_no)
  3   global partition by range (order_no)
  4    (partition IND1 values less than (555555),
  5     partition IND2 values less than (MAXVALUE)
  6    )
  7  ;
 global partition by range (order_no)
                                    *
ERROR at line 3:
ORA-14038: GLOBAL partitioned index must be prefixed

接下来再来看看本地分区。

本地索引的分区和其对应的表分区数量相等,因此每个表分区都对应着相应的索引分区。使用本地索引,不需要指定分区范围因为索引对于表而言是本地的,当本地索引创建时,Oracle会自动为表中的每个分区创建独立的索引分区。

创建本地索引不必显式的指定maxvalue值,因为为表新添加表分区时,会自动添加相应的索引分区。

create index orders_local_1_idx
on orders(ord_date)
 local
  (partition LOCAL1,
   partition LOCAL2,
   partition LOCAL3,
   partition LOCAL4
  )
;

Index created.

SQL> select INDEX_NAME, PARTITION_NAME from dba_ind_partitions where index_name=upper('orders_local_1_idx');

INDEX_NAME                     PARTITION_NAME
------------------------------ ------------------------------
ORDERS_LOCAL_1_IDX             LOCAL1
ORDERS_LOCAL_1_IDX             LOCAL2
ORDERS_LOCAL_1_IDX             LOCAL3
ORDERS_LOCAL_1_IDX             LOCAL4

SQL> alter table orders add partition Q5 values less than (TO_DATE('01-APR-2000','DD-MON-YYYY'));

Table altered.

SQL> select INDEX_NAME, PARTITION_NAME from dba_ind_partitions where index_name=upper('orders_local_1_idx');

INDEX_NAME                     PARTITION_NAME
------------------------------ ------------------------------
ORDERS_LOCAL_1_IDX             LOCAL1
ORDERS_LOCAL_1_IDX             LOCAL2
ORDERS_LOCAL_1_IDX             LOCAL3
ORDERS_LOCAL_1_IDX             LOCAL4
ORDERS_LOCAL_1_IDX             Q5

这里系统已经自动以和表分区相同的名字自动创建了一个索引分区。同理,删除表分区时相对应的索引分区也自动被删除。

本地索引和全局索引还有一个显著的差别,就是上面提到的,本地索引可以创建成本地非前缀型,而全局索引只能是前缀型。

SQL> create index orders_local_2_idx
     on orders(part_no)
      local
       (partition LOCAL1,
        partition LOCAL2,
        partition LOCAL3,
        partition LOCAL4)
     ;

Index created.

SQL> select INDEX_NAME, PARTITION_NAME, HIGH_VALUE from dba_ind_partitions
     where index_name=upper('orders_local_2_idx');

INDEX_NAME                     PARTITION_NAME                 HIGH_VALUE
------------------------------ ------------------------------ ---------------------------------------------------------
ORDERS_LOCAL_2_IDX             LOCAL1                         TO_DATE(' 1999-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS',
                                                              'NLS_CALENDAR=GREGORIA'
ORDERS_LOCAL_2_IDX             LOCAL2                         TO_DATE(' 1999-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS',
                                                              'NLS_CALENDAR=GREGORIA'
ORDERS_LOCAL_2_IDX             LOCAL3                         TO_DATE(' 1999-10-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS',
                                                              'NLS_CALENDAR=GREGORIA'
ORDERS_LOCAL_2_IDX             LOCAL4                         TO_DATE(' 2000-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS',
                                                              'NLS_CALENDAR=GREGORIA'

从上面的输出可以看出,虽然索引的键值是part_no,但索引分区的键值仍然和表的分区键值相同,即ord_date,也即是所谓的非前缀型索引。

最后,再引用一个例子说明前缀索引和非前缀索引的应用。

假设有一个使用DATE列分区的大表。我们经常使用一个VARCHAR2列(VCOL)进行查询,但这个列并不是表的分区键值。

有两种可能的方法来访问VCOL列的数据,一是建立基于VCOL列的本地非前缀索引,

               |                                         |
             -------                                   -------
            |       |         (10 more                |        |
Values:     A..     Z..   partitions here)            A..      Z..

另一种是建立基于VCOL列的全局索引,

                |                                         |
              -------                                   -------
             |       |         (10 more                |        |
Values:      A..     D..   partitions here)            T..      Z..

可以看出,如果能够保证VCOL列值的唯一性,全局索引将会是最好的选择。如果VCOL列值不唯一,就需要在本地非前缀索引的并行查询和全局索引顺序查询以及高昂的维护代价之间做出选择。

posted @ 2014-05-06 17:29 鸿雁 阅读(207) | 评论 (0)编辑 收藏

解决ORA-12560: TNS: 协议适配器错误

造成ORA-12560: TNS: 协议适配器错误的问题的原因可能有如下三个:
1.监听服务没有起起来。windows平台个一如下操作:开始---程序---管理工具---服务,打开服务面板,

启动oraclehome92TNSlistener服务。
2.database instance没有起起来。windows平台如下操作:开始---程序---管理工具---服务,打开服务

面板,启动oracleserviceXXXX,XXXX就是你的database SID.
3.注册表问题。regedit,然后进入HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\HOME0将该环境变量ORACLE_SI

D设置为XXXX,XXXX就是你的database SID.或者右几我的电脑,属性--高级--环境变量---系统变量--新建

,变量名=oracle_sid,变量值=XXXX,XXXX就是你的database SID.或者进入sqlplus前,在command line下

输set oracle_sid=XXXX,XXXX就是你的database SID.
经过以上步骤,就可以解决问题。

posted @ 2014-05-05 10:46 鸿雁 阅读(156) | 评论 (0)编辑 收藏

Oracle优化全攻略一(Oracle SQL Hint)

其实Oracle的优化器有两种优化方式,
基于规则的优化方式(Rule-Based Optimization,简称为RBO)
基于代价的优化方式(Cost-Based Optimization,简称为CBO)
所以hint也不例外,除了/*+rule*/其他的都是CBO优化方式
RBO方式:
  优化器在分析SQL语句时,所遵循的是Oracle内部预定的一些规则。比如我们常见的,当一个where子句中的一列有索引时去走索引。
CBO方式:
   它是看语句的代价(Cost),这里的代价主要指Cpu和内存。优化器在判断是否用这种方式时,主要参照的是表及索引的统计信息。统计信息给出表的大小、有少行、每行的长度等信息。这些统计信息起初在库内是没有的,是做analyze后才出现的,很多的时侯过期统计信息会令优化器做出一个错误的执行计划,因些应及时更新这些信息。

优化模式包括Rule、Choose、First rows、All rows四种方式:

    Rule:基于规则的方式。

    Choolse:默认的情况下Oracle用的便是这种方式。指的是当一个表或或索引有统计信息,则走CBO的方式,如果表或索引没统计信息,表又不是特别的小,而且相应的列有索引时,那么就走索引,走RBO的方式。

    First Rows:它与Choose方式是类似的,所不同的是当一个表有统计信息时,它将是以最快的方式返回查询的最先的几行,从总体上减少了响应时间。

    All Rows:也就是我们所说的Cost的方式,当一个表有统计信息时,它将以最快的方式返回表的所有的行,从总体上提高查询的吞吐量。没有统计信息则走RBO的方式

Oracle在那配置默认的优化规则
    A、Instance级别我们可以通过在initSID.ora文件中设定OPTIMIZER_MODE=RULE/CHOOSE/FIRST_ROWS/ALL_ROWS如果没设定OPTIMIZER_MODE参数则默认用的是Choose方式。
    B、Sessions级别通过ALTER SESSION SET OPTIMIZER_MODE=RULE/CHOOSE/FIRST_ROWS/ALL_ROWS来设定。
    C、语句级别用Hint(/*+ ... */)来设定
为什么表的某个字段明明有索引,但执行计划却不走索引?
    1、优化模式是all_rows的方式
    2、表作过analyze,有统计信息
    3、表很小,Oracle的优化器认为不值得走索引。
提示
   不区分大小写, 多个提示用空格分开
  如:select /*+ hint1(tab1) hint2(TAB1 idx1) */ col1, col2 from tab1 where col1='xxx';
  如果表使用了别名, 那么提示里也必须使用别名
如:select /*+ hint1(t1) */ col1, col2 from tab1 t1 where col1='xxx';
如果使用同一个表的多个用,号分开
如: select /*+ index(t1.A,t1.B) */ col1, col2
    from   tab1 t1
    where  col1='xxx';

oracle 10g hints知识,
    10g数据库可以使用更多新的optimizer hints来控制优化行为。现在让我们快速解析一下这些强大的新hints:

1、spread_min_analysis

   使用这一hint,你可以忽略一些关于如详细的关系依赖图分析等电子表格的编译时间优化规则。其他的一些优化,如创建过滤以有选择性的定位电子表格访问结构并限制修订规则等,得到了继续使用。

   由于在规则数非常大的情况下,电子表格分析会很长。这一提示可以帮助我们减少由此产生的数以百小时计的编译时间。

例:
    SELECT /*+ SPREAD_MIN_ANALYSIS */ ...

2、spread_no_analysis

   通过这一hint,可以使无电子表格分析成为可能。同样,使用这一hint可以忽略修订规则和过滤产生。如果存在一电子表格分析,编译时间可以被减少到最低程度。

例:
    SELECT /*+ SPREAD_NO_ANALYSIS */ ...

3、use_nl_with_index

   这项hint使CBO通过嵌套循环把特定的表格加入到另一原始行。只有在以下情况中,它才使用特定表格作为内部表格:如果没有指定标签,CBO必须可以使用一些标签,且这些标签至少有一个作为索引键值加入判断;反之,CBO必须能够使用至少有一个作为索引键值加入判断的标签。

例:
  SELECT /*+ USE_NL_WITH_INDEX (polrecpolrind) */ ...

4、CARDINALITY

  此hint定义了对由查询或查询部分返回的基数的评价。注意如果没有定义表格,基数是由整个查询所返回的总行数。

例:
  SELECT /*+ CARDINALITY ( [tablespec] card ) */

5、SELECTIVITY

  此hint定义了对查询或查询部分选择性的评价。如果只定义了一个表格,选择性是在所定义表格里满足所有单一表格判断的行部分。如果定义了一系列表格,选择性是指在合并以任何顺序满足所有可用判断的全部表格后,所得结果中的行部分。

例:
   SELECT /*+ SELECTIVITY ( [tablespec] sel ) */

然而,注意如果hints CARDINALITY 和 SELECTIVITY都定义在同样的一批表格,二者都会被忽略。

6、no_use_nl

  Hint no_use_nl使CBO执行循环嵌套,通过把指定表格作为内部表格,把每个指定表格连接到另一原始行。通过这一hint,只有hash join和sort-merge joins会为指定表格所考虑。

例:
   SELECT /*+ NO_USE_NL ( employees ) */ ...

7、no_use_merge

  此hint使CBO通过把指定表格作为内部表格的方式,拒绝sort-merge把每个指定表格加入到另一原始行。

例:
  SELECT /*+ NO_USE_MERGE ( employees dept ) */ ...

8、no_use_hash

  此hint使CBO通过把指定表格作为内部表格的方式,拒绝hash joins把每个指定表格加入到另一原始行。

例:
  SELECT /*+ NO_USE_HASH ( employees dept ) */ ...

9、no_index_ffs

  此hint使CBO拒绝对指定表格的指定标签进行fast full-index scan。
Syntax: /*+ NO_INDEX_FFS ( tablespecindexspec ) */


在SQL优化过程中常见HINT的用法(前10个比较常用, 前3个最常用):

1. /*+ INDEX */ 和 /*+ INDEX(TABLE INDEX1, index2) */ 和 /*+ INDEX(tab1.col1 tab2.col2) */ 和 /*+ NO_INDEX */ 和 /*+ NO_INDEX(TABLE INDEX1, index2) */

表明对表选择索引的扫描方法. 第一种不指定索引名是让oracle对表中可用索引比较并选择某个最佳索引; 第二种是指定索引名且可指定多个索引; 第三种是10g开始有的, 指定列名, 且表名可不用别名; 第四种即全表扫描; 第五种表示禁用某个索引, 特别适合于准备删除某个索引前的评估操作. 如果同时使用了INDEX和NO_INDEX则两个提示都会被忽略掉.
例如:SELECT /*+ INDEX(BSEMPMS SEX_INDEX) USE SEX_INDEX BECAUSE THERE ARE FEWMALE BSEMPMS */ FROM BSEMPMS WHERE SEX='M';

2. /*+ ORDERED */
FROM子句中默认最后一个表是驱动表,ORDERED将from子句中第一个表作为驱动表. 特别适合于多表连接非常慢时尝试.
例如:SELECT /*+ ORDERED */ A.COL1,B.COL2,C.COL3 FROM TABLE1 A,TABLE2 B,TABLE3 C WHERE A.COL1=B.COL1 AND B.COL1=C.COL1;

3. /*+ PARALLEL(table1,DEGREE) */ 和 /*+ NO_PARALLEL(table1) */
该提示会将需要执行全表扫描的查询分成多个部分(并行度)执行, 然后在不同的操作系统进程中处理每个部分. 该提示还可用于DML语句. 如果SQL里还有排序操作, 进程数会翻倍,此外还有一个一个负责组合这些部分的进程,如下面的例子会产生9个进程. 如果在提示中没有指定DEGREE, 那么就会使用创建表时的默认值. 该提示在默认情况下会使用APPEND提示. NO_PARALLEL是禁止并行操作,否则语句会使用由于定义了并行对象而产生的并行处理.
例如:select /*+ PARALLEL(tab_test,4) */ col1, col2 from tab_test order by col2;

4. /*+ FIRST_ROWS */ 和 /*+ FIRST_ROWS(n) */
表示用最快速度获得第1/n行, 获得最佳响应时间, 使资源消耗最小化.
在update和delete语句里会被忽略, 使用分组语句如group by/distinct/intersect/minus/union时也会被忽略.
例如:SELECT /*+ FIRST_ROWS */ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='SCOTT';

5. /*+ RULE */
表明对语句块选择基于规则的优化方法.
例如:SELECT /*+ RULE */ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='SCOTT';

6. /*+ FULL(TABLE) */
表明对表选择全局扫描的方法.
例如:SELECT /*+ FULL(A) */ EMP_NO,EMP_NAM FROM BSEMPMS A WHERE EMP_NO='SCOTT';

7. /*+ LEADING(TABLE) */
类似于ORDERED提示, 将指定的表作为连接次序中的驱动表.

8. /*+ USE_NL(TABLE1,TABLE2) */
将指定表与嵌套的连接的行源进行连接,以最快速度返回第一行再连接,与USE_MERGE刚好相反.
例如:SELECT /*+ ORDERED USE_NL(BSEMPMS) */ BSDPTMS.DPT_NO,BSEMPMS.EMP_NO,BSEMPMS.EMP_NAM FROM BSEMPMS,BSDPTMS WHERE BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;

9. /*+ APPEND */ 和 /*+ NOAPPEND */
直接插入到表的最后,该提示不会检查当前是否有插入操作所需的块空间而是直接添加到新块中, 所以可以提高速度. 当然也会浪费些空间, 因为它不会使用那些做了delete操作的块空间. NOAPPEND提示则相反,所以会取消PARALLEL提示的默认APPEND提示.
例如:insert /*+ append */ into test1 select * from test4;
insert /*+ parallel(test1) noappend */ into test1 select * from test4;

10. /*+ USE_HASH(TABLE1,table2) */
将指定的表与其它行源通过哈希连接方式连接起来.为较大的结果集提供最佳响应时间. 类似于在连接表的结果中遍历每个表上每个结果的嵌套循环, 指定的hash表将被放入内存, 所以需要有足够的内存(hash_area_size或pga_aggregate_target)才能保证语句正确执行, 否则将在磁盘里进行.
例如:SELECT /*+ USE_HASH(BSEMPMS,BSDPTMS) */ * FROM BSEMPMS,BSDPTMS WHERE BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;

---------------------------------------------------------------------

11. /*+ USE_MERGE(TABLE) */
将指定的表与其它行源通过合并排序连接方式连接起来.特别适合于那种在多个表大量行上进行集合操作的查询, 它会将指定表检索到的的所有行排序后再被合并, 与USE_NL刚好相反.
例如:SELECT /*+ USE_MERGE(BSEMPMS,BSDPTMS) */ * FROM BSEMPMS,BSDPTMS WHERE BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;

12. /*+ ALL_ROWS */
表明对语句块选择基于开销的优化方法,并获得最佳吞吐量,使资源消耗最小化. 可能会限制某些索引的使用.
例如:SELECT /*+ ALL+_ROWS */ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='SCOTT';

13. /*+ CLUSTER(TABLE) */
提示明确表明对指定表选择簇扫描的访问方法. 如果经常访问连接表但很少修改它, 那就使用集群提示.
例如:SELECT /*+ CLUSTER */ BSEMPMS.EMP_NO,DPT_NO FROM BSEMPMS,BSDPTMS WHERE DPT_NO='TEC304' AND BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;

14. /*+ INDEX_ASC(TABLE INDEX1, INDEX2) */
表明对表选择索引升序的扫描方法. 从8i开始, 这个提示和INDEX提示功能一样, 因为默认oracle就是按照升序扫描索引的, 除非未来oracle还推出降序扫描索引.
例如:SELECT /*+ INDEX_ASC(BSEMPMS PK_BSEMPMS) */ FROM BSEMPMS WHERE DPT_NO='SCOTT';

15. /*+ INDEX_COMBINE(TABLE INDEX1, INDEX2) */
指定多个位图索引, 对于B树索引则使用INDEX这个提示,如果INDEX_COMBINE中没有提供作为参数的索引,将选择出位图索引的布尔组合方式.
例如:SELECT /*+ INDEX_COMBINE(BSEMPMS SAL_BMI HIREDATE_BMI) */ * FROM BSEMPMS WHERE SAL<5000000 AND HIREDATE<SYSDATE;

16. /*+ INDEX_JOIN(TABLE INDEX1, INDEX2) */
合并索引, 所有数据都已经包含在这两个索引里, 不会再去访问表, 比使用索引并通过rowid去扫描表要快5倍.
例如:SELECT /*+ INDEX_JOIN(BSEMPMS SAL_HMI HIREDATE_BMI) */ SAL,HIREDATE FROM BSEMPMS WHERE SAL<60000;

17. /*+ INDEX_DESC(TABLE INDEX1, INDEX2) */
表明对表选择索引降序的扫描方法.
例如:SELECT /*+ INDEX_DESC(BSEMPMS PK_BSEMPMS) */ FROM BSEMPMS WHERE DPT_NO='SCOTT';

18. /*+ INDEX_FFS(TABLE INDEX_NAME) */
对指定的表执行快速全索引扫描,而不是全表扫描的办法.要求要检索的列都在索引里, 如果表有很多列时特别适用该提示.
例如:SELECT /*+ INDEX_FFS(BSEMPMS IN_EMPNAM) */ * FROM BSEMPMS WHERE DPT_NO='TEC305';

19. /*+ NO_EXPAND */
对于WHERE后面的OR 或者IN-LIST的查询语句,NO_EXPAND将阻止其基于优化器对其进行扩展, 缩短解析时间.
例如:SELECT /*+ NO_EXPAND */ * FROM BSEMPMS WHERE DPT_NO='TDC506' AND SEX='M';

20. /*+ DRIVING_SITE(TABLE) */
强制与ORACLE所选择的位置不同的表进行查询执行.特别适用于通过dblink连接的远程表.
例如:SELECT /*+ DRIVING_SITE(DEPT) */ * FROM BSEMPMS,DEPT@BSDPTMS DEPT WHERE BSEMPMS.DPT_NO=DEPT.DPT_NO;

21. /*+ CACHE(TABLE) */ 和 /*+ NOCACHE(TABLE) */
当进行全表扫描时,CACHE提示能够将表全部缓存到内存中,这样访问同一个表的用户可直接在内存中查找数据. 比较适合数据量小但常被访问的表, 也可以建表时指定cache选项这样在第一次访问时就可以对其缓存. NOCACHE则表示对已经指定了CACHE选项的表不进行缓存.
例如:SELECT /*+ FULL(BSEMPMS) CAHE(BSEMPMS) */ EMP_NAM FROM BSEMPMS;

22. /*+ PUSH_SUBQ */
当SQL里用到了子查询且返回相对少的行时, 该提示可以尽可能早对子查询进行评估从而改善性能, 不适用于合并连接或带远程表的连接.
例如:select /*+ PUSH_SUBQ */ emp.empno, emp.ename, itemno from emp, orders where emp.empno = orders.empno and emp.deptno = (select deptno from dept where loc='XXX');
远程连接其他数据库,注意判断数据库是否启动,或者是否有需要的表,否则会出错

23. /*+ INDEX_SS(TABLE INDEX1,INDEX2) */
指示对特定表的索引使用跳跃扫描, 即当组合索引的第一列不在where子句中时, 让其使用该索引
参考资料
Oracle SQL hints
  /*+ hint */
/*+ hint(argument) */
/*+ hint(argument-1 argument-2) */
All hints except /*+ rule */ cause the CBO to be used. Therefore, it is good practise to analyze the underlying tables if hints are used (or the query is fully hinted. There should be no schema names in hints. Hints must use aliases if alias names are used for table names. So the following is wrong:
select /*+ index(scott.emp ix_emp) */ from scott.emp emp_alias
better:
select /*+ index(emp_alias ix_emp) */ ... from scott.emp emp_alias
Why using hints
It is a perfect valid question to ask why hints should be used. Oracle comes with an optimizer that promises to optimize a query's execution plan. When this optimizer is really doing a good job, no hints should be required at all. Sometimes, however, the characteristics of the data in the database are changing rapidly, so that the optimizer (or more accuratly, its statistics) are out of date. In this case, a hint could help. It must also be noted, that Oracle allows to lock the statistics when they look ideal which should make the hints meaningless again.
Hint categories
Hints can be categorized as follows:
Hints for Optimization Approaches and Goals,
Hints for Access Paths, Hints for Query Transformations,
Hints for Join Orders,
Hints for Join Operations,
Hints for Parallel Execution,
Additional Hints

Documented Hints
Hints for Optimization Approaches and Goals
ALL_ROWS
One of the hints that 'invokes' the Cost based optimizer
ALL_ROWS is usually used for batch processing or data warehousing systems.
FIRST_ROWS
One of the hints that 'invokes' the Cost based optimizer
FIRST_ROWS is usually used for OLTP systems.
CHOOSE
One of the hints that 'invokes' the Cost based optimizer
This hint lets the server choose (between ALL_ROWS and FIRST_ROWS, based on statistics gathered.
RULE
The RULE hint should be considered deprecated as it is dropped from Oracle9i2.
See also the following initialization parameters: optimizer_mode, optimizer_max_permutations, optimizer_index_cost_adj, optimizer_index_caching and
Hints for Access Paths
CLUSTER
Performs a nested loop by the cluster index of one of the tables.
FULL
Performs full table scan.
HASH
Hashes one table (full scan) and creates a hash index for that table. Then hashes other table and uses hash index to find corresponding records. Therefore not suitable for < or > join conditions.
ROWID
Retrieves the row by rowid
INDEX
Specifying that index index_name should be used on table tab_name: /*+ index (tab_name index_name) */
Specifying that the index should be used the the CBO thinks is most suitable. (Not always a good choice).
Starting with Oracle 10g, the index hint can be described: /*+ index(my_tab my_tab(col_1, col_2)) */. Using the index on my_tab that starts with the columns col_1 and col_2.
INDEX_ASC
INDEX_COMBINE
INDEX_DESC
INDEX_FFS
INDEX_JOIN
NO_INDEX
AND_EQUAL
The AND_EQUAL hint explicitly chooses an execution plan that uses an access path that merges the scans on several single-column indexes

Hints for Query Transformations
FACT
The FACT hint is used in the context of the star transformation to indicate to the transformation that the hinted table should be considered as a fact table.
MERGE
NO_EXPAND
NO_EXPAND_GSET_TO_UNION
NO_FACT
NO_MERGE
NOREWRITE
REWRITE
STAR_TRANSFORMATION
USE_CONCAT

Hints for Join Operations
DRIVING_SITE
HASH_AJ
HASH_SJ
LEADING
MERGE_AJ
MERGE_SJ
NL_AJ
NL_SJ
USE_HASH
USE_MERGE
USE_NL

Hints for Parallel Execution
NOPARALLEL
PARALLEL
NOPARALLEL_INDEX
PARALLEL_INDEX
PQ_DISTRIBUTE

Additional Hints
ANTIJOIN
APPEND
If a table or an index is specified with nologging, this hint applied with an insert statement produces a direct path insert which reduces generation of redo.
BITMAP
BUFFER
CACHE
CARDINALITY
CPU_COSTING
DYNAMIC_SAMPLING
INLINE
MATERIALIZE
NO_ACCESS
NO_BUFFER
NO_MONITORING
NO_PUSH_PRED
NO_PUSH_SUBQ
NO_QKN_BUFF
NO_SEMIJOIN
NOAPPEND
NOCACHE
OR_EXPAND
ORDERED
ORDERED_PREDICATES
PUSH_PRED
PUSH_SUBQ
QB_NAME
RESULT_CACHE (Oracle 11g)
SELECTIVITY
SEMIJOIN
SEMIJOIN_DRIVER
STAR
The STAR hint forces a star query plan to be used, if possible. A star plan has the largest table in the query last in the join order and joins it with a nested loops join on a concatenated index. The STAR hint applies when there are at least three tables, the large table's concatenated index has at least three columns, and there are no conflicting access or join method hints. The optimizer also considers different permutations of the small tables.
SWAP_JOIN_INPUTS
USE_ANTI
USE_SEMI

Undocumented hints:
BYPASS_RECURSIVE_CHECK
Workaraound for bug 1816154
BYPASS_UJVC
CACHE_CB
CACHE_TEMP_TABLE
CIV_GB
COLLECTIONS_GET_REFS
CUBE_GB
CURSOR_SHARING_EXACT
DEREF_NO_REWRITE
DML_UPDATE
DOMAIN_INDEX_NO_SORT
DOMAIN_INDEX_SORT
DYNAMIC_SAMPLING
DYNAMIC_SAMPLING_EST_CDN
EXPAND_GSET_TO_UNION
FORCE_SAMPLE_BLOCK
GBY_CONC_ROLLUP
GLOBAL_TABLE_HINTS
HWM_BROKERED
IGNORE_ON_CLAUSE
IGNORE_WHERE_CLAUSE
INDEX_RRS
INDEX_SS
INDEX_SS_ASC
INDEX_SS_DESC
LIKE_EXPAND
LOCAL_INDEXES
MV_MERGE
NESTED_TABLE_GET_REFS
NESTED_TABLE_SET_REFS
NESTED_TABLE_SET_SETID
NO_FILTERING
NO_ORDER_ROLLUPS
NO_PRUNE_GSETS
NO_STATS_GSETS
NO_UNNEST
NOCPU_COSTING
OVERFLOW_NOMOVE
PIV_GB
PIV_SSF
PQ_MAP
PQ_NOMAP
REMOTE_MAPPED
RESTORE_AS_INTERVALS
SAVE_AS_INTERVALS
SCN_ASCENDING
SKIP_EXT_OPTIMIZER
SQLLDR
SYS_DL_CURSOR
SYS_PARALLEL_TXN
SYS_RID_ORDER
TIV_GB
TIV_SSF
UNNEST
USE_TTT_FOR_GSETS

posted @ 2014-05-04 21:21 鸿雁 阅读(198) | 评论 (0)编辑 收藏

Oracle 执行计划(Explain Plan) 说明

     摘要:       如果要分析某条SQL的性能问题,通常我们要先看SQL的执行计划,看看SQL的每一步执行是否存在问题。 如果一条SQL平时执行的好好的,却有一天突然性能很差,如果排除了系统资源和阻塞的原因,那么基本可以断定是执行计划出了问题。          &nb...  阅读全文

posted @ 2014-05-03 11:52 鸿雁 阅读(247) | 评论 (0)编辑 收藏

使用EXPLAIN PLAN获取SQL语句执行计划

     摘要: SQL查询语句的性能从一定程度上影响整个数据库的性能。很多情况下,数据库性能的低下差不多都是不良SQL语句所引起。而SQL语句的执行计划则决定了SQL语句将会采用何种方式从数据库提取数据并返回给客户端,本文描述的将是如何通过EXPLAIN PLAN 获取SQL语句执行计划来获取SQL语句的执行计划。一、获取SQL语句执行计划的方式     1. 使用expl...  阅读全文

posted @ 2014-05-03 11:44 鸿雁 阅读(147) | 评论 (0)编辑 收藏

执行计划的使用(EXPLAIN)

对于sql执行的小量高低。我们可以通过执行计划的信息基本上可以进行分析查看该SQL语句执行的时间。连接顺序及浪费的数据库资源等信息,从而判断该SQL语句执行的效率如何,下面就简单的介绍一下执行计划的使用

  2.        Explain使用

  Oracle RDBMS执行每一条SQL语句,都必须经过Oracle优化器的评估。所 以,了解优化器是如何选择(搜索)路径以及索引是如何被使用的,对优化SQL语句有很大的帮助。Explain可以用来迅速方便地查出对于给定SQL语句中的查询数据是如何得到的即搜索路径(我们通常称为Access Path)。从而使我们选择最优的查询方式达到最大的优化效果。

  2.1.        安装

  要使用执行计划首先需要执行相应的脚本。

  使用Explain工具需要创建Explain_plan表,这必须先进入相关应用表、视图和索引的所有者的帐户内。Oracle的介质中包含有执行此项工作的SQL源程序,例如:

  ORA_RDBMS: XPLAINPL.SQL (VMS)

  $ORACLE_HOME/rdbms/admin/utlxplan.sql (UNIX)

  该脚本后会生成一个表这个程序会创建一个名为plan_table的表,表结构如下:

  我们简单的介绍一下主要的字段含义:

字段名              字段类型            含义
STATEMENT_ID    VARCHAR2(30)        explain PLAN 语句中所指定的最优STATEMENT_ID 参数值, 如果在EXPLAN PLAN语句中没有使用SET STATEMENT_ID,那么此值会被设为NULL。
REMARKS         VARCHAR2(80)        与被解释规划的各步骤相关联的注释最长可达80 字节
OPERATION       VARCHAR2(30)        各步骤所执行内部操作的名称在某条语句所产生的第一行中该列的可能取值如下DELETE STATEMENT INSERT STATEMENT SELECT STATEMENT UPDATE STATEMENT
OPTIONS         VARCHAR2(30)        对OPERATION 列中所描述操作的变种
OBJECT_NODE     VARCHAR2(128)       用于访问对象的数据库链接database link 的名称对于使用并行执行的本地查询该列能够描述操作中输出的次序
OBJECT_OWNER    VARCHAR2(30)        对于包含有表或索引的架构schema 给出其所有者的名称
OBJECT_NAME     VARCHAR2(30)        表或索引的名称
OBJECT_INSTANCE  INTEGER            根据对象出现在原始original 语句中的次序所给出的相应次序编号就原始的语句文本而论其处理顺序为自左至右自外向内景象扩张view
OBJECT_TYPE     VARCHAR2(30)        用于提供对象描述性信息的修饰符例如索引的NON-UNIQUE
OPTIMIZER       VARCHAR2(255)       当前优化程序的模式
ID              INTEGER             分配给执行规划各步骤的编号
PARENT_ID       INTEGER             对ID 步骤的输出进行操作的下一个执行步骤的ID
POSITION        INTEGER             对于具有相同PARENT_ID 的步骤其相应的处理次序
COST            INTEGER             根据优化程序的基于开销的方法所估计出的操作开销值对于使用基于规则方法的语句该列为空该列值没有特定的测量单位它只是一个用于比较执行规划开销大小的权重值
CARDINALITY     INTEGER             根据基于开销的方法对操作所访问行数的估计值
BYTES           INTEGER             根据基于开销的方法对操作所访问字节的估计

  2.2.        使用

  2.2.1.        常规使用

  常规使用语法:

explain PLAN [ SET STATEMENT_ID [=] < string literal > ]
[ INTO < table_name > ]
FOR < sql_statement >
其中:
STATEMENT_ID是一个唯一的字符串,把当前执行计划与存储在同一PLAN表中的其它执行计划区别开来。
TABLE_NAME是plan表名,它结构如前所示,你可以任意设定这个名称。
SQL_STATEMENT是真正的SQL语句。
如:
SQL> explain plan set statement_id='test1' for
  2     SELECT a.soctermbegin,
  3            a.soctermend,
  4            a.dealserialno,
  5            a.levydataid,
  6            a.dealtotal,
  7            e.categoryitemcode,
  8            row_number() over(PARTITION BY a.levydataid ORDER BY 1) AS theRow
  9       FROM tb_soc_packdealdata   a,
10            tb_Lvy_TaxDataBillMap c,
11            Tb_lvy_BillData       d,
12            tb_soc_levydetaildata e
13      WHERE a.levydataid = c.datafrompointer(+)
14        AND c.billdataid = d.billdataid(+)
15        AND a.levydataid = e.levydataid
16        AND a.packdealstatuscode = '10'
17        AND (a.datastatus <> '9' OR a.datastatus is NULL)
18        AND (d.billstatus IS NULL OR
19            (d.billstatus <> '2' AND d.billstatus <> '8'))
20        AND a.Insurcode = '6010952'
21  ;
Explained

  执行下面语句就可以查看该语句执行的执行计划:

SQL> SELECT A.OPERATION,OPTIONS,OBJECT_NAME,OBJECT_TYPE,ID,PARENT_ID
  2  FROM PLAN_TABLE  a
  3  WHERE STATEMENT_ID='test1'
  4  ORDER BY Id;
OPERATION        OPTIONS        OBJECT_NAME                    OBJECT_TYPEID  PARENT_ID
---------------- --------------------------------------------- ------------- ----------
SELECT STATEMENT                                                           0
WINDOW           SORT                                                      1          0
FILTER                                                                     2          1
NESTED LOOPS     OUTER                                                     3          2
NESTED LOOPS     OUTER                                                     4          3
NESTED LOOPS                                                               5          4
TABLE ACCESS     FULL           TB_SOC_PACKDEALDATA                        6          5
TABLE ACCESS     BY INDEX ROWID TB_SOC_LEVYDETAILDATA                      7          5
INDEX            RANGE SCAN     IND_DATAID_LEVSOC              NON-UNIQUE  8          7
TABLE ACCESS     BY INDEX ROWID TB_LVY_TAXDATABILLMAP                      9          4
INDEX            RANGE SCAN     TBLVYTAXDATABIL_DATAFROMPOINTE NON-UNIQUE 10          9
TABLE ACCESS     BY INDEX ROWID TB_LVY_BILLDATA                           11          3
INDEX            UNIQUE SCAN    TBLVYBILLDATA_BILLDATAID       UNIQUE

  2.2.2.        自动显示使用

  在SQLPLUS中自动跟踪显示执行计划及相关信息
  SQL>set timing on  --显示执行时间
  SQL>set autorace on ?C显示执行计划
  SQL>set autorace on ?C显示执行计划
  SQL>set autotrace traceonly ?C只显示执行计划即不显示查询出来的数据

  设置完毕后执行SQL语句就会显示执行计划信息及相应的统计信息(需要设置显示该选项)

SQL> select nvl(sum(t.taxdue), 0)
  2             from tb_lvy_sbzs100 t, tb_lvy_declaredoc a, tb_lvy_declaredoc b
  3            where a.dossiercode = 'SB02041108'
  4              and a.pages = 123
  5              and a.remarkid = b.remarkid
  6              AND A.REMARKID IS NOT NULL
  7              and b.declaredocid = t.declaredocid;
NVL(SUM(T.TAXDUE),0)
--------------------
                   0
                  
  已用时间:  00: 00: 04.07
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=110)
   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS (Cost=6 Card=1 Bytes=110)
   3    2       MERGE JOIN (CARTESIAN) (Cost=4 Card=1 Bytes=74)
   4    3         TABLE ACCESS (FULL) OF 'TB_LVY_SBZS100' (Cost=2 Card =1 Bytes=31)
   5    3         BUFFER (SORT) (Cost=2 Card=1 Bytes=43)
   6    5           TABLE ACCESS (FULL) OF 'TB_LVY_DECLAREDOC' (Cost=2 Card=1 Bytes=43)
   7    2       TABLE ACCESS (BY INDEX ROWID) OF 'TB_LVY_DECLAREDOC' (Cost=2 Card=1 Bytes=36)
   8    7         INDEX (UNIQUE SCAN) OF 'TBLVYDECLAREDOC_DECLAREDOCID' (UNIQUE)
Statistics
----------------------------------------------------------
          0  recursive calls --循环递归次数
          0  db block gets―请求的数据块在buffer能满足的个数
       6675  consistent gets --逻辑IO用于读表并计算行数, 数据请求总数在回滚段Buffer中
         45  physical reads ?C从磁盘读到Buffer Cache数据块数量
          0  redo size ?C产生的redo日志大小
        217  bytes sent via SQL*Net to client
        276  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed
SQL>

  如果6675  consistent gets --逻辑IO用于读表并计算行数, 数据请求总数在回滚段Buffer中

  45        physical reads ?C从磁盘读到Buffer Cache数据块数量的数值比较小则该语句对对数据库的性能比较高。

  2.2.3.        PL/SQL和TOAD中使用

  如果在PL/SQL中使用选择要查询语句显示执行计划,则只需要SQL WINDOWS 窗口里面输入要查询的SQL语句,然后选择按键F5或者在菜单TOOLS?D?D>Explain Plan 菜单按键就可以在执行计划窗口查看该语句的执行计划。

  在TOAD语句中在执行当前的SQL窗口中选择下方的Explain PlanTAB页即可以查看要执行语句的执行计划信息。

  2.3.        限制

  虽然任何SQL语句都可以用explain解释,但对于没有查询的INSERT,UPDATE,DELETE操作来说,这个工具并没有太大的用处。没有子查询的INSERT操作不会创建执行计划,但没有WHERE子句或子查询的UPDATE和DELETE操作会创建执行计划,因为这些操作必须先找出所要的记录。

  另外,如果你在SQL语句中使用其它类型如sequence等,explain也能揭示它的用法。

  explain真正的唯一的限制是用户不能去解释其它用户的表,视图,索引或其它类型,用户必须是所有被解释事物的所有者,如果不是所有者而只有select权限,explain会返回一个错误。

posted @ 2014-05-03 11:36 鸿雁 阅读(132) | 评论 (0)编辑 收藏

《Oracle物化视图实战手册》

Oracle物化视图实战手册》

场合:数据变化小,查询出数据还要2次利用,需要数据双向同步的场合

视图:就是一条sql语句,每次查询时都要重新生成执行计划,重新执行,非常消耗时间,放在内存中一次性的

物化视图:执行sql并保留结果,直接放在数据文件中,不放在内存中方便重用【空间换时间】,不受开关机的影响

1.创建基表并插入数据

create table sino_person_address

(

iid NUMBER(16) not null,

ipersonid NUMBER(16),

spin NUMBER(16),

dgettime DATE,

sorgcode VARCHAR2(20),

smsgfilename VARCHAR2(20),

ilineno NUMBER(8),

saddress VARCHAR2(60),

szip CHAR(6),

scondition CHAR(1),

itrust NUMBER(1),

stoporgcode VARCHAR2(14),

istate NUMBER(1),

constraint PK_SINO_PERSON_ADDRESS primary key (iid)

);

插入数据(插入自动增长序列号字段的方法)

INSERT 语句插入这个字段值为: 序列号的名称.NEXTVAL,seq_sino_person_address.nextval

insert into sino_person_address values(seq_sino_person_address.nextval,123,to_date('2013-04-08 12:12:12','yyyy-mm-dd hh24:mi:ss'),'110','test_report',111,'beijing

xicheng','100100','1',123,1,'1000',0);

insert into sino_person_address values(seq_sino_person_address.nextval,123,to_date('2013-04-09 12:12:12','yyyy-mm-dd hh24:mi:ss'),'120','test_report2',121,'beijing

xicheng','100200','2',123,1,'1002',2);

insert into sino_person_address values(seq_sino_person_address.nextval,123,to_date('2013-04-10 12:12:12','yyyy-mm-dd hh24:mi:ss'),'130','test_report3',131,'beijing

xicheng','100300','3',123,1,'1003',3);

commit

###################################################################################################

2.创建物化视图日志

意义:记录基表DML操作的变化,实时刷新物化视图

注:包含所有字段

删除物化视图日志

drop materialized view log on t

创建物化视图日志

create materialized view log on t with sequence,rowid (x,y,z) including new values;

参数说明:

with sequence:以序号增1的方式进行变化记录

rowid (x,y,z):定位哪些数据发生了变化,日志记录rowid指向的数据块的位置和变化

删除物化视图日志

drop materialized view log on sino_person_address;

基于主键方式的刷新,创建物化视图日志

CREATE MATERIALIZED VIEW LOG ON sino_person_address

WITH PRIMARY KEY

INCLUDING NEW VALUES

【TABLESPACE sinojfs2】; 可选项

3.创建物化视图

创建物化视图

create materialized view mv_t build immediate refresh fast on commit enable query rewrite as select x,y,z,count(*) from t group by x,y,z;

删除物化视图

drop materialized view mv_sino_person_address;

create materialized view mv_sino_person_address

tablespace SINOJFS2

build immediate 创建物化视图时,立即刷新基表

refresh fast with primary key 支持基于主键的快速刷新(增量刷新),基表必须有主键

on commit 支持commit动作自动刷新

enable query rewrite

as select * from sino_person_address;

create materialized view mv_sino_person_address

tablespace SINOJFS2

build immediate

refresh fast with primary key refresh complete全部刷新【全表刷新】可选项

on demand 支持需求时手工刷新

enable query rewrite

as select * from sino_person_address;

########################################################################################

参数说明:

build immediate:创建物化视图时,立即刷新基表

refresh fast with primary key:支持基于主键的快速刷新(增量刷新),基表必须有主键

on commit:基于commit动作的自动刷新 on demand:基于需求时的手工刷新

enable query rewrite:支持查询重新(使用物化视图代替基表,查询必须重写,查询重写是透明的并且不需要对物化视图有任何权限,物化视图可以启用和禁用查询重写)

查询重写:select * from t基表,执行计划走的是mv_t物化视图,禁用后,执行计划走的就是t基表了

tablespace SINOJFS2 创建于SINOJFS2表空间

(1)创建方式:BUILD IMMEDIATE(立即生成数据), BUILD DEFERRED(下一次刷新时生新数据), ON PREBUILD TABLE(不创建新的数据段,用已存在的含有当前物化视图数据的表来代替);  
(2)ENABLE | DISABLE QUERY REWRITE指定是否启用当前物化视图用于查询重写,启用该选项时,系统会检查以保证查询的可确定性(不允许有如序列数,USER, DATE等不确定的返回值),DISABLE时物化视图照样可以被刷新;    
与物化视图生效相关的设置    
(1)初始化参数JOB_QUEUE_PROCESSES设置大于零,物化的自动刷新操作需要JOB QUEUE进程来执行;    
(2)初始化参数OPTIMIZER_MODE要设成某种CBO优化模式;    
(3)用户会话有QUERY_REWRITE(优化器能将查询重写到本方案物化视图)或GLOBAL_QUERY_REWRITE(优化器能将查询重写到其它方案的物化视图)系统权限;    
(4)初始化参数QUERY_REWRITE_ENABLED 指示优化器是否动态重写查询来使用物化视图,这个参数可以在四个级别上进行设置(参数文件,ALTER SYSTEM, ALTER SESSION, HINTS);    
(5)初始化参数QUERY_REWRITE_INTEGRITY 指示优化器在不同的数据一致性情况下决定是否使用物化视图来重写查询,ENFORCED(只有在能确保数据一致的前提下才使用物化视图), TRUSTED(数据不一定一致,只要有用维度对象定义的关系存在,就可使用物化视图), STALE_TOLERATED(数据不一致,也没有相关的维度定义时仍可使用物化视图),这个参数可以在三个级别上进行设置(参数文件,ALTER SYSTEM, ALTER SESSION);

4. 物化视图DML操作测试

(1)验证物化视图是否随记录增加而增加

insert into sino_person_address values(seq_sino_person_address.nextval,123,to_date('2013-04-11 13:13:13','yyyy-mm-dd hh24:mi:ss'),'140','test_report4',141,'beijing

xicheng','100400','4',123,1,'1004',4);

select * from sino_person_address order by dgetdate;

select * from mv_sino_person_address order by dgetdate; 随记录增加而木有刷新,必须commit之后才触发物化视图刷新,没有问题

exec dbms_mview.refresh('mv_sino_person_address','c'); 还可以手动全部刷新【全表刷新】(先清除,再重装数据)

exec dbms_mview.refresh('mv_sino_person_address','f'); 也可以快速刷新【增量刷新】借助物化视图日志,只检查自上次刷新后改变了的数据来进行刷新)

(2)验证物化视图是否随记录删除而减少

delete from sino_person_address where iid=21;

select * from sino_person_address order by dgetdate;

select * from mv_sino_person_address order by dgetdate; 随记录删除而木有刷新,必须commit之后才触发物化视图刷新,没有问题

exec dbms_mview.refresh('mv_sino_person_address','c'); 还可以手动全部刷新【全表刷新】(先清除,再重装数据)

exec dbms_mview.refresh('mv_sino_person_address','f'); 也可以快速刷新【增量刷新】借助物化视图日志,只检查自上次刷新后改变了的数据来进行刷新)

(3)验证物化视图是否随记录修改而更新

update sino_person_address set sorgcode='200' where sorgcode='120';

select * from sino_person_address order by dgetdate;

select * from mv_sino_person_address order by dgetdate; 随记录修改而木有刷新,必须commit之后才触发物化视图刷新,没有问题

exec dbms_mview.refresh('mv_sino_person_address','c'); 还可以手动全部刷新【全表刷新】(先清除,再重装数据)

exec dbms_mview.refresh('mv_sino_person_address','f'); 也可以快速刷新【增量刷新】(借助物化视图日志,只检查自上次刷新后改变了的数据来进行刷新)

(4)验证物化视图是否随truncate而清空

truncate table sino_person_address;

select * from sino_person_address order by dgetdate;

select * from mv_sino_person_address order by dgetdate; 随记录truncate而木有清空,必须手动truncate table mv_sino_person_address;才能清空(两者是没有关联的),没有问题

5.物化视图刷新

根据业务需求,每月定时刷新。根据以上条件,选择使用ORACLE自带工具DBMS_MVIEW工具包中REFRESH方法对物化视图进行刷新。该方法有两个参数,第一个参数是需要刷新的物化视图名称,第二个参数是刷新方式。我们可以写存储过程,对每个物化视图调用一次REFRESH方法,也可以使用“,”把物化视图连接以来,一次刷新。

定义存储过程

create or replace procedure pro_mview_refresh

as

begin

dbms_mview.refresh('mv_sino_person_address','f');

end;

/

执行存储过程

execute pro_mview_refresh;

还可以刷新所有物化视图 dbms_mview.refresh_all_mviews;

创建存储过程

drop procedure pro_refresh_all_mviews;

create or replace procedure pro_refresh_all_mviews

as

i number;

begin

dbms_mview.refresh_all_mviews(number_of_failures=>i);

dbms_output.put_line('number_of_failures=>'||i);

end;

/

执行

executepro_refresh_all_mviews;

set serveroutput on;不可放在存储过程中,因为这是sqlplus命令,如果你怕忘记或者嫌麻烦可以把set serveroutput on;

写入/opt/oracle/product/11.2.0/dbhome_1/sqlplus/admin/glogin.sql中,每次使用sqlplus时自动加载这个文件

如果想用PL/SQL Developer工具访问数据库,请在C:\Program Files\PLSQL Developer\Login.sql 文件里添加

-- Autostart Command Window script

set serveroutput on;

这样以后再使用PL/SQL Developer工具访问数据库就可以自动加载这条命令了

###############################################################################################

研发人员专用,手动刷新,想刷就刷

set serveroutput on; 打开屏幕显示功能,就可以看到number_of_failures=>0结果

PL/SQL 匿名块

declare

i number;

begin

dbms_mview.refresh_all_mviews(number_of_failures=>i);

dbms_output.put_line('number_of_failures=>'||i);

end;

/

number_of_failures=>0

Number_of_failures 表示刷新物化视图失败个数

采用默认refresh force 刷新方式:先试图用FAST方式刷新,如果失败再用COMPLETE方式刷新,这是默认的刷新方式

注意:

1、 如果需要同时刷新多个物化视图,必须用逗号把各个物化视图名称连接起来,并对每个视图都要指明刷新方式(f、增量刷新,c、完全刷新,?、强制刷新,从不刷新)。

NEVER REFRESH(不刷新)

REFREST FAST(借助物化视图日志,只检查自上次刷新后改变了的数据来进行刷新)

REFRESH COMPLETE(先清除,再重装数据)

REFRESH FORCE(先试图用FAST方式刷新,如果失败再用COMPLETE方式刷新,这是默认的刷新方式)

确定刷新时机:

ON COMMIT(事务提交时刷新),

ON DEMAND(用DBMS_MVIEW.REFRESH, DBMS_MVIEW.REFRESH_DEPENDENT, DBMS_MVIEW.REFRESH_ALL_MVIEWS来手工刷新),

By Time(用START WITH 和 NEXT 子句创建的job来定时自动刷新);

[dbms_mview.refresh('mv_sino_person_address,mv_person_address_his','ff');]

2、当日志和物化视图创建好后,删除日志,则需要重新创建物化视图,否则无法增量刷新。

drop materialized view log on sino_person_address; 删除日志

SQL> exec dbms_mview.refresh('mv_sino_person_address','c'); 删除物化视图日志,只可以支持物化视图全部刷新

PL/SQL procedure successfully completed

#################################################################################

SQL> exec dbms_mview.refresh('mv_sino_person_address','f'); 无法增量刷新

begin dbms_mview.refresh('mv_sino_person_address','f'); end;

ORA-23413: 表 "SINOJFS"."SINO_PERSON_ADDRESS" 没有实体化视图日志

ORA-06512: 在 "SYS.DBMS_SNAPSHOT", line 2558

ORA-06512: 在 "SYS.DBMS_SNAPSHOT", line 2771

ORA-06512: 在 "SYS.DBMS_SNAPSHOT", line 2740

ORA-06512: 在 line 2

SQL> create materialized view log on sino_person_employment 重新创建物化视图日志

2 with primary key

3 including new values;

Materialized view log created

SQL> exec dbms_mview.refresh('mv_sino_person_employment','f'); 但还是不支持增量刷新,因为日志内容和原表内容不一致了

begin dbms_mview.refresh('mv_sino_person_employment','f'); end;

ORA-12034: "SINOJFS"."SINO_PERSON_EMPLOYMENT" 上的实体化视图日志比上次刷新后的内容新

ORA-06512: 在 "SYS.DBMS_SNAPSHOT", line 2558

ORA-06512: 在 "SYS.DBMS_SNAPSHOT", line 2771

ORA-06512: 在 "SYS.DBMS_SNAPSHOT", line 2740

ORA-06512: 在 line 2

因为:丢失了删除日志那一点->重建日志那一点之间的原表DML变化,因此日志内容和原表内容不一致了

解决方案:重建物化视图日志 重新【增量刷新】和【全表刷新】一遍

SQL> drop materialized view log on sino_loan_compact; 删除日志

Materialized view log dropped

SQL> create materialized view log on sino_loan_compact 重建日志

2 with primary key

3 including new values;

Materialized view log created

SQL> exec dbms_mview.refresh('mv_sino_loan_compact','c'); 必须先全表刷新

PL/SQL procedure successfully completed

SQL> exec dbms_mview.refresh('mv_sino_loan_compact','f'); 再增量刷新,否则ORA-12034: "SINOJFS"."SINO_LOAN_COMPACT" 上的实体化视图日志比上次刷新后的内容新

PL/SQL procedure successfully completed

小结:只要能够增量刷新,说明日志没有问题了

简述所有视图的快速刷新和全表刷新命令(测试使用) 10张视图

select owner,table_name,tablespace_name,status from dba_tables where table_name in ('SINO_LOAN_APPLY');

update SINO_LOAN_APPLY set sorgcode ='1000' where iid =858;

exec dbms_mview.refresh('mv_sino_loan_compact','c');

exec dbms_mview.refresh('mv_sino_loan_compact','f');

exec dbms_mview.refresh('mv_sino_loan_apply','c');

exec dbms_mview.refresh('mv_sino_loan_apply','f');

exec dbms_mview.refresh('mv_sino_loan_spec_trade','c');

exec dbms_mview.refresh('mv_sino_loan_spec_trade','f');

exec dbms_mview.refresh('mv_sino_loan','c');

exec dbms_mview.refresh('mv_sino_loan','f');

exec dbms_mview.refresh('mv_sino_loan_guarantee','c');

exec dbms_mview.refresh('mv_sino_loan_guarantee','f');

exec dbms_mview.refresh('mv_sino_loan_investor','c');

exec dbms_mview.refresh('mv_sino_loan_investor','f');

###############################################################################

exec dbms_mview.refresh('mv_sino_person_employment','c');

exec dbms_mview.refresh('mv_sino_person_employment','f');

exec dbms_mview.refresh('mv_sino_person_address','c');

exec dbms_mview.refresh('mv_sino_person_address','f');

exec dbms_mview.refresh('mv_sino_person_certification','c');

exec dbms_mview.refresh('mv_sino_person_certification','f');

exec dbms_mview.refresh('mv_sino_person','c');

exec dbms_mview.refresh('mv_sino_person','f');

3.基表增加字段后对应物化视图不能自动同步结构

业务表增加 上报状态 字段 ipbcstate number(1) 可以为空

文档 建模 脚本 物化视图

sino_person_certification 完成 完成 完成 完成

sino_person 完成 完成 完成 完成

sino_person_address 完成 完成 完成 完成

sino_person_employment 完成 完成 完成 完成

sino_person_address_his 完成 完成 完成

sino_person_employment_his 完成 完成 完成

sino_person_his 完成 完成 完成

sino_loan 完成 完成 完成 完成

sino_loan_compact 完成 完成 完成 完成

sino_loan_spec_trade 完成 完成 完成 完成

sino_loan_guarantee 完成 完成 完成 完成

sino_loan_investor 完成 完成 完成 完成

sino_loan_apply 完成 完成 完成 完成

对比IPBCSTATE 字段基表有,但物化视图没有,需要重建物化视图解决

select * from mv_sino_loan_compact where rownum<2;

select * from sino_loan_compact where rownum<2;

select * from mv_sino_loan where rownum < 2;

select * from sino_loan where rownum < 2;

select * from mv_sino_loan_apply where rownum < 2;

select * from sino_loan_apply where rownum < 2;

select * from mv_sino_loan_guarantee where rownum < 2;

select * from sino_loan_guarantee where rownum < 2;

select * from mv_sino_loan_guarantee where rownum < 2;

select * from sino_loan_guarantee where rownum < 2;

select * from mv_sino_loan_investor where rownum < 2;

select * from sino_loan_investor where rownum < 2;

select * from mv_sino_loan_spec_trade where rownum < 2;

select * from sino_loan_spec_trade where rownum < 2;

################################################################################

select * from mv_sino_person where rownum < 2;

select * from sino_person where rownum < 2;

select * from mv_sino_person_address where rownum < 2;

select * from sino_person_address where rownum < 2;

select * from mv_sino_person_certification where rownum < 2;

select * from sino_person_certification where rownum < 2;

select * from mv_sino_person_employment where rownum < 2;

select * from sino_person_employment where rownum < 2;

##################################################################################

4.因为上面写的物化视图是基于主键进行刷新的,因此原表必须要有主键

6.定时刷新JOB

确定执行时间间隔  
1)、 每分钟执行    
Interval => TRUNC(sysdate,'mi') + 1 / (24*60)    
2)、 每天定时执行    
例如:每天下午2点执行一次pro_mview_refresh存储过程    
Interval => TRUNC(sysdate) + 1 +14/ (24)    
3)、 每周定时执行    
例如:每周一凌晨2点执行    
Interval => TRUNC(next_day(sysdate,2))+2/24 --星期一,一周的第二天    
4)、 每月定时执行    
例如:每月1日凌晨2点执行    
Interval =>TRUNC(LAST_DAY(SYSDATE))+1+2/24    
5)、 每季度定时执行    
例如每季度的第一天凌晨2点执行    
Interval => TRUNC(ADD_MONTHS(SYSDATE,3),'Q') + 2/24    
6)、 每半年定时执行    
例如:每年7月1日和1月1日凌晨2点    
Interval => ADD_MONTHS(trunc(sysdate,'yyyy'),6)+2/24    
7)、 每年定时执行    
例如:每年1月1日凌晨2点执行    
Interval =>ADD_MONTHS(trunc(sysdate,'yyyy'),12)+2/24

通过jobs的使用就能实现每天或每月的指定时间执行一个函数、过程与命令

set serveroutput on 启动屏幕输出功能

SQL> execute dbms_output.put_line('This is'); 已经可以正常输出

This is

创建作业

variable job_num number; 定义存储job编号的变量

declare job_num number; pro_refresh_all_mviews

begin

dbms_job.submit

(job=>:job_num,

what=>'pro_refresh_all_mviews;',

next_date=>sysdate,

interval=>'sysdate+1/1440'); 每天1440分钟,每一分钟运行pro_mview_refresh过程一次

dbms_output.put_line('Job Number is'||to_char(job_num));

commit;

end;

/

############################################################################################

绑定变量版,必须先定义变量

variable job_num number;

declare job_num number;

begin

dbms_job.submit

(job=>:job_num,

what=>'pro_refresh_all_mviews;',

next_date=>sysdate,

interval=>'trunc(SYSDATE+5/1440,''MI'')'); 每5分钟运行一次job

dbms_output.put_line('Job Number is'||to_char(job_num));

commit;

end;

/

例如:每天上午10点执行一次pro_refresh_all_mviews存储过程  
Interval => TRUNC(sysdate) + 1 +10/ (24)    
declare job_num number;

begin

dbms_job.submit

(job=>:job_num,

what=>'pro_refresh_all_mviews;',

next_date=>sysdate,

interval=>'trunc(SYSDATE)+1+10/24'); 每天上午10点运行一次job

dbms_output.put_line('Job Number is'||to_char(job_num));

commit;

end;

/

Job Number is

PL/SQL procedure successfully completed

job_num

---------

1

####################################################################################

PL/SQL 匿名块版,可以直接在块中定义变量,比较方面现在采用这种

declare

job_num number;

begin

dbms_job.submit

(job=>job_num,

what=>'pro_refresh_all_mviews;',

next_date=>sysdate,

interval=>'trunc(SYSDATE)+1+10/24');

dbms_output.put_line('Job Number is '||job_num);

commit;

end;

/

Job Number is 4

PL/SQL procedure successfully completed

####################################################################################

dbms_job.submit( job out binary_integer,  
what in varchar2,    
next_date in date,    
interval in varchar2,    
no_parse in boolean)

●job:输出变量,这是作业在作业队列中的编号;  
●what:执行作业的存储过程及其输入参数;    
●next_date:作业初次执行的时间;    
●interval:作业执行的时间间隔。指上一次执行结束到下一次开始执行的时间间隔    
其中Interval这个值是决定Job何时,被重新执行的关键;当interval设置为null时,该job执行结束后,就被从队列中删除。假如我们需要该job周期性地执行,则要用‘sysdate+m’表示。如何更好地确定执行时间的间隔需要我们掌握一个函数TRUNC。

SQL> show parameter job_queue_process 作业队列进程数,oracle能够并发job数量,0~1000

NAME TYPE VALUE

------------------------------------ ----------- ------------------------------

job_queue_processes integer 1000

Oracle提供的数据字典user_jobs监控作业状态

SQL> select job,log_user,what,last_date,last_sec,next_date,next_sec,failures,broken from user_jobs;

Job 作业唯一编号

Log_user 提交作业的用户

What 作业执行的存储过程

Last_date 最后一次成功运行作业的日期

Last_sec 最后一次成功运行作业的时间

Next_date 下一次运行作业日期

Next_sec 下一次运行作业时间

Failures 执行失败次数,当执行job出现错误时,Oracle将其记录在日志里,失败次数每次自动加1,加到16之后Oracle就不在执行它了

Broken 是否是异常作业,当执行失败次数达到16时,Oracle就将该job标志为broken。此后,Oracle不再继续执行它,直到用户调用过程dbms_job.broken,重新设置为not broken,或强制调用dbms_job.run来重新执行它。Y标示作业中断,以后不会运行,N表示作业正常,可以运行

运行作业  
begin    
         dbms_job.run(:job_num); job_num是存储job编号的变量    
end;    
查询作业状态    
SQL> select job,log_user,what,last_date,last_sec,next_date,next_sec,failures,broken from user_jobs;

JOB LOG_USER WHAT LAST_DATE LAST_SEC NEXT_DATE NEXT_SEC FAILURES BROKEN

---------- --------- ---------------------------------------------------- ----------- ---------------- -----------

1 SINOJFS pro_refresh_all_mviews; 2013-4-26 1 11:27:38 2013-4-27 1 10:00:00 0 N

Job 作业唯一编号

Log_user 提交作业的用户

What 作业执行的存储过程

Last_date 最后一次成功运行作业的日期

Last_sec 最后一次成功运行作业的时间

Next_date 下一次运行作业日期

Next_sec 下一次运行作业时间

Failures 执行失败次数,当执行job出现错误时,Oracle将其记录在日志里,失败次数每次自动加1,加到16之后Oracle就不在执行它了

Broken 是否是异常作业,当执行失败次数达到16时,Oracle就将该job标志为broken。此后,Oracle不再继续执行它,直到用户调用过程dbms_job.broken,重新设置为not broken;

或强制调用dbms_job.run来重新执行它。Y标示作业中断,以后不会运行,N表示作业正常,可以运行

删除作业  
begin    
         dbms_job.remove(:job_num);    
end;

修改作业

dbms_job.remove(jobno); 删除job号

例 execute dbms_job.remove(1);

######################################################################

dbms_job.what(jobno,what); 修改执行的存储过程

dbms_job.next_date(job,next_date)修改下次执行的时间

例 exec dbms_job.next_date(46,sysdate+2/(24*60)); 46作业号

#####################################################################

dbms_job.interval(job,interval)   :修改间隔时间

例 exec dbms_job.interval(46,sysdate+3/(24*60));

######################################################################  
dbms_job.broken(job,true)     中断job

例 exec dbms_job.broken(46,true); 46作业号 exec dbms_job.broken(2,true) BROKEN=Y

#######################################################################

dbms_job.broken(job,false,next_date)   next_date:下次执行时间,如果不填则马上启动job

例 exec dbms_job.broken(46,false); 启动job exec dbms_job.broken(2,false); BROKEN=N

########################################################################

dbms_job.run(jobno); 运行作业

例子 execute dbms_job.run(1);

posted @ 2014-05-02 23:03 鸿雁 阅读(250) | 评论 (0)编辑 收藏

Oracle 物化视图 说明

一.    物化视图概述

Oracle的物化视图是包括一个查询结果的数据库对像,它是远程数据的的本地副本,或者用来生成基于数据表求和的汇总表。物化视图存储基于远程表的数据,也可以称为快照。

 

物化视图可以用于预先计算并保存表连接或聚集等耗时较多的操作的结果,这样,在执行查询时,就可以避免进行这些耗时的操作,而从快速的得到结果。物化视图有很多方面和索引很相似:使用物化视图的目的是为了提高查询性能;物化视图对应用透明,增加和删除物化视图不会影响应用程序中SQL语句的正确性和有效性;物化视图需要占用存储空间;当基表发生变化时,物化视图也应当刷新。

 

物化视图可以查询表,视图和其它的物化视图。

 

通常情况下,物化视图被称为主表(在复制期间)或明细表(在数据仓库中)。

对于复制,物化视图允许你在本地维护远程数据的副本,这些副本是只读的。如果你想修改本地副本,必须用高级复制的功能。当你想从一个表或视图中抽取数据时,你可以用从物化视图中抽取。

对于数据仓库,创建的物化视图通常情况下是聚合视图,单一表聚合视图和连接视图。

在复制环境下,创建的物化视图通常情况下主键,rowid,和子查询视图。

 

物化视图由于是物理真实存在的,故可以创建索引。

 

 

1.1 物化视图可以分为以下三种类型

(1)    包含聚集的物化视图;

(2)    只包含连接的物化视图;

(3)    嵌套物化视图。

 

三种物化视图的快速刷新的限制条件有很大区别,而对于其他方面则区别不大。创建物化视图时可以指定多种选项,下面对几种主要的选择进行简单说明:

 

1)创建方式(BuildMethods):包括BUILD IMMEDIATE和BUILD DEFERRED两种。

BUILD IMMEDIATE是在创建物化视图的时候就生成数据。

BUILD DEFERRED则在创建时不生成数据,以后根据需要在生成数据。默认为BUILD IMMEDIATE

 

2)查询重写(QueryRewrite):包括ENABLE QUERY REWRITE和DISABLE QUERY REWRITE两种。

分别指出创建的物化视图是否支持查询重写。查询重写是指当对物化视图的基表进行查询时,Oracle会自动判断能否通过查询物化视图来得到结果,如果可以,则避免了聚集或连接操作,而直接从已经计算好的物化视图中读取数据。默认为DISABLEQUERY REWRITE

 

3刷新(Refresh):指当基表发生了DML操作后,物化视图何时采用哪种方式和基表进行同步。刷新的模式有两种:ON DEMAND和ON COMMIT。

ON DEMAND和ON COMMIT物化视图的区别在于其刷新方法的不同,ON DEMAND指物化视图在用户需要的时候进行刷新,可以手工通过DBMS_MVIEW.REFRESH等方法来进行刷新,也可以通过JOB定时进行刷新,即更新物化视图,以保证和基表数据的一致性;而ON COMMIT是说,一旦基表有了COMMIT,即事务提交,则立刻刷新,立刻更新物化视图,使得数据和基表一致。

对基表,平常的COMMIT在0.01秒内可以完成,但在有了ON COMMIT视图后,居然要6秒。速度减低了很多倍。ON COMMIT视图对基表的影响可见一斑。

 

1.2 物化视图,根据不同的着重点可以有不同的分类:

1)        按刷新方式分:FAST/COMPLETE/FORCE

2)        按刷新时间的不同:ON DEMAND/ON COMMIT

3)        按是否可更新:UPDATABLE/READ ONLY

4)        按是否支持查询重写:ENABLE QUERY REWRITE/DISABLEQUERY REWRITE

 

默认情况下,如果没指定刷新方法和刷新模式,则Oracle默认为FORCE和DEMAND。

 

注意:设置REFRESH ON COMMIT的物化视图不能访问远端对象。

 

在建立物化视图的时候可以指定ORDER BY语句,使生成的数据按照一定的顺序进行保存。不过这个语句不会写入物化视图的定义中,而且对以后的刷新也无效。

 

1.3 物化视图有三种刷新方式:COMPLETEFAST和 FORCE

1)        完全刷新(COMPLETE)会删除表中所有的记录(如果是单表刷新,可能会采用TRUNCATE的方式),然后根据物化视图中查询语句的定义重新生成物化视图。

2)        快速刷新(FAST)采用增量刷新的机制,只将自上次刷新以后对基表进行的所有操作刷新到物化视图中去。FAST必须创建基于主表的视图日志。

            对于增量刷新选项,如果在子查询中存在分析函数,则物化视图不起作用。

3)        采用FORCE方式,Oracle会自动判断是否满足快速刷新的条件,如果满足则进行快速刷新,否则进行完全刷新。

 

Oracle物化视图的快速刷新机制是通过物化视图日志完成的。Oracle通过一个物化视图日志还可以支持多个物化视图的快速刷新。

物化视图日志根据不同物化视图的快速刷新的需要,可以建立为ROWID或PRIMARY KEY类型的。还可以选择是否包括SEQUENCE、INCLUDING NEW VALUES以及指定列的列表。

 

1.4 物化视图Refresh子句的其他说明与示例

 

REFRESH 子句可以包含如下部分:

           [refresh [fast|complete|force]

           [on demand | commit]

           [start with date] [next date]

           [with {primary key|rowid}]]

 

1.4.1 主键和ROWD子句:

WITH PRIMARY KEY选项生成主键物化视图,也就是说物化视图是基于主表的主键,而不是ROWID(对应于ROWID子句). PRIMARY KEY是默认选项,为了生成PRIMARY KEY子句,应该在主表上定义主键,否则应该用基于ROWID的物化视图.

 

基于ROWID物化视图只有一个单一的主表,不能包括下面任何一项:

(1).Distinct 或者聚合函数.

(2)    .Group by,子查询,连接和SET操作

 

--主键(PrimaryKey)物化视图示例:

在远程数据库表emp上创建主键物化视图:

 

  1. CREATEMATERIALIZEDVIEW mv_emp_pk  
  2. REFRESHFASTSTARTWITHSYSDATE  
  3. NEXT  SYSDATE + 1/48  
  4. WITHPRIMARYKEY  
  5. ASSELECT * FROM emp@remote_db  

 

 

--当用FAST选项创建物化视图,必须创建基于主表的视图日志,如下:

  1. CREATEMATERIALIZEDVIEWLOGON emp;  


 

--Rowid物化视图示例:

下面的语法在远程数据库表emp上创建Rowid物化视图

  1. CREATEMATERIALIZEDVIEW mv_emp_rowid  
  2. REFRESHWITHROWID  
  3. ASSELECT * FROM emp@remote_db;  
  4. Materializedviewlog created.  

 

--子查询物化视图示例:

在远程数据库表emp上创建基于emp和dept表的子查询物化视图

  1. CREATEMATERIALIZEDVIEW  mv_empdept  
  2. ASSELECT * FROM emp@remote_db e  
  3. WHEREEXISTS  
  4. (SELECT * FROM dept@remote_db d  
  5. WHEREe.dept_no = d.dept_no)  

 

 

1.4.2 刷新时间

START WITH子句通知数据库完成从主表到本地表第一次复制的时间,应该及时估计下一次运行的时间点, NEXT 子句说明了刷新的间隔时间.

 

  1. CREATEMATERIALIZEDVIEW mv_emp_pk  
  2. REFRESHFAST  
  3. STARTWITHSYSDATE  
  4. NEXT  SYSDATE + 2  
  5. WITHPRIMARYKEY  
  6. ASSELECT * FROM emp@remote_db;  

 

 

在上面的例子中,物化视图数据的第一个副本在创建时生成,以后每两天刷新一次.

 

  1. creatematerializedviewMV_LVY_LEVYDETAILDATA  
  2. TABLESPACE ZGMV_DATA --保存表空间   
  3. BUILDDEFERRED--延迟刷新不立即刷新   
  4. refreshforce--如果可以快速刷新则进行快速刷新,否则完全刷新   
  5. ondemand--按照指定方式刷新   
  6. startwith to_date('24-11-200518:00:10''dd-mm-yyyyhh24:mi:ss'--第一次刷新时间   
  7. nextTRUNC(SYSDATE+1)+18/24--刷新时间间隔   
  8. as  
  9. SELECT * FROM emp@remote_db;  


 

1.5 ON PREBUILD TABLE 说明

在创建物化视图时指明ON PREBUILD TABLE语句,可以将物化视图建立在一个已经存在的表上。这种情况下,物化视图和表必须同名。当删除物化视图时,不会删除同名的表。

这种物化视图的查询重写要求参数QUERY_REWRITE_INTEGERITY必须设置为trusted或者stale_tolerated

 

1.6 物化视图分区

物化视图可以进行分区。而且基于分区的物化视图可以支持分区变化跟踪(PCT)。具有这种特性的物化视图,当基表进行了分区维护操作后,仍然可以进行快速刷新操作。对于聚集物化视图,可以在GROUP BY列表中使用CUBE或ROLLUP,来建立不同等级的聚集物化视图。

 

 

相关内容参考:

Oracle 物化视图日志 与 快速刷新 说明

http://blog.csdn.net/tianlesoftware/article/details/7720580

 

Oracle 物化视图 详细错误描述 查看方法

http://blog.csdn.net/tianlesoftware/article/details/7719789

 

Oracle 物化视图 快速刷新 限制 说明

http://blog.csdn.net/tianlesoftware/article/details/7719679

 

 

二.    物化视图操作示例

 

1.     创建物化视图需要的权限:

 

  1. GRANT CREATE MATERIALIZED VIEW TO USER_NAME;  


 

2.  在源表建立物化视图日志  

  1. CREATE MATERIALIZED VIEW LOG ON DAVE  
  2. TABLESPACE&BISONCU_SPACE           -- 日志空间   
  3. WITH PRIMARY KEY;                   -- 指定为主键类型  

 

 

3.  授权给中间用户  

  1. GRANT SELECT ON DAVE TO ANQING;  
  2. GRANT SELECT ON MLOG$_DAVE TO ANQING;  


 

4.  在目标数据库上创建MATERIALIZED VIEW  

 

  1. CREATE MATERIALIZED VIEW AICS_DAVE  
  2. TABLESPACE&BISONCS_SPACE  
  3. REFRESH FAST  
  4.    ON DEMAND  
  5.    --第一次刷新时间   
  6.    --START WITH to_date('2012-01-01 20:00:00', 'yyyy-mm-dd hh24:mi:ss')   
  7.    START WITH SYSDATE  
  8.    --刷新时间间隔。每1天刷新一次,时间为凌晨2点   
  9.    --NEXT TRUNC(SYSDATE,'dd')+1+2/24   
  10.    NEXT SYSDATE+1/24/20  
  11. WITH PRIMARY KEY  
  12. --USING DEFAULT LOCAL ROLLBACKSEGMENT   
  13. DISABLE QUERY REWRITE AS  
  14. SELECT MODEL_ID, STATUS,MODEL_NAME, MANU_ID, DESCRIPTION, CREATE_TIME, UPDATE_TIME, SW_VERSION  
  15. FROM AICS_DAVE@LINK_DAVE;  

 

5.  在目标物化视图上创建索引 

 

  1. CREATE INDEX IDX_T_DV_CT  
  2.    ON AICS_DEV_INFO (CREATE_TIME, UPDATE_TIME)  
  3.    TABLESPACE &BISON_IDX;  
  4.    
  5. CREATE INDEX IDX_T_DV_UT  
  6.    ON AICS_DEV_INFO (UPDATE_TIME)  
  7.    TABLESPACE &BISON_IDX;  
  8.    
  9. CREATE INDEX I_T_DV_MSISDN  
  10.    ON AICS_DEV_INFO (MSISDN)  
  11.    TABLESPACE &BISON_IDX;  


 

 

6. 物化视图刷新说明

1)使用dbms_mview.refresh 手工刷新

如:

  1. EXEC DBMS_MVIEW.REFRESH('MV_DAVE');  
  2.    
  3. --完全刷新   
  4. EXEC DBMS_MVIEW.REFRESH(LIST => 'MV_DAVE',METHOD => 'c');  
  5. EXEC DBMS_MVIEW.REFRESH('MV_DAVE','C');  
  6.    
  7. --快速刷新   
  8. EXEC DBMS_MVIEW.REFRESH(LIST => 'MV_DAVE',METHOD => 'f');  
  9. EXEC DBMS_MVIEW.REFRESH('MV_DAVE','F');  


 

 

2)使用dbms_refresh.refresh 过程来批量刷新MV

          如果我们在创建物化视图的过程指定start 和next time的刷新时间,那么Oracle 会自动创建刷新的job,并采用dbms_refresh.refresh 的方式。

            使用这种方式刷新之前需要先make refresh group,然后才可以刷新。

 

            Refreshmake 的语法可以参考:

http://docs.oracle.com/cd/B19306_01/server.102/b14227/rarrefreshpac.htm#i94057

 

示例:

            假设存在物化视图MV_T1, MV_T2, MV_T3. 创建refresh group的语法如下:

  1. SQL> EXEC DBMS_REFRESH.MAKE('REP_TEST''MV_T1,MV_T2,MV_T3', SYSDATE, 'SYSDATE+ 1')  
  2.    
  3. --刷新整个refresh group 组:   
  4. SQL> EXEC DBMS_REFRESH.REFRESH('REP_TEST')  

 

 

7. 删除物化视图及日志

  1. --删除物化视图日志:   
  2. DROP MATERIALIZED VIEW LOG ON DAVE;  
  3. --删除物化视图   
  4. DROP MATERIALIZED VIEW MV_DAVE;  

 

 

8. 查看物化视图刷新状态信息

 

  1. SQL> SELECT MVIEW_NAME, LAST_REFRESH_DATE, STALENESS FROMUSER_MVIEWS;  
  2. SQL> SELECT NAME, LAST_REFRESH FROM USER_MVIEW_REFRESH_TIMES;  

 

 

9. 查询物化视图日志:

 

  1. SELECT * FROM MLOG$_DAVE;  


 

 

posted @ 2014-05-02 23:02 鸿雁 阅读(148) | 评论 (0)编辑 收藏

忘记oracle的sys用户密码怎么修改

一、忘记除SYS、SYSTEM用户之外的用户的登录密码。
 用SYS (或SYSTEM)用户登录: CONN SYS/PASS_WORD AS SYSDBA;
 使用如下语句修改用户的密码: ALTER USER user_name IDENTIFIED BY "newpass";
 注意:密码不能全是数字。并且不能是数字开头。否则会出现:ORA-00988: 口令缺失或无效
二、忘记SYS用户,或者是SYSTEM用户的密码。
 如果是忘记SYSTEM用户的密码,可以用SYS用户登录。然后用ALTER USER 命令修改密码:
 CONN SYS/PASS_WORD AS SYSDBA;
 ALTER USER SYSTEM IDENTIFIED BY "newpass";
 如果是忘记SYS用户的密码,可以用SYSTEM用户登录。然后用ALTER USER 命令修改密码。
 CONN SYSTEM/PASS_WORD ;
 ALTER USER SYSTEM IDENTIFIED BY "newpass";
三、如果SYS,SYSTEM用户的密码都忘记或是丢失。
 可以使用ORAPWD.EXE 工具修改密码。
 开始菜单-&gt;运行-&gt;输入‘CMD’,打开命令提示符窗口,输入如下命令:
 orapwd file=D:\oracle10g\database\pwdctcsys.ora password=newpass
 这个命令重新生成了数据库的密码文件。密码文件的位置在ORACLE_HOME目录下的\database目录下。
 这个密码是修改sys用户的密码。除sys其他用户的密码不会改变。
不过Oracle提供了两种验证方式,一种是OS验证,另一种密码文件验证方式,如果是第一种方式用以下方法修改密码:
  sqlplus /nolog;
  connect / as sysdba
  alter user sys identified by ;
  alter user system identified by ;
  如果是第二种方法就用上述方式修改,也可以下方法修改密码:
  orapwd file=pwdxxx.ora password=你设定的新密码 entries=10
  设定完后,重新启动服务,再次登陆就可以了。
oracle 11g
在本机安装完Oracle以后,不记得sys用户的密码了,采用如下方法可以修改密码:
1.打开cmd,输入sqlplus /nolog,回车;输入“conn / as sysdba”;输入“alter user sys identified by 新密码”,注意:新密码最好以字母开头,否则可能出现错误Ora-00988。有了这个方法后,只要自己对oracle服务器有管理员权限,不记得密码的时候就可以随意修改密码了。
2.在命令行执行如下命令:sqlplus “/@服务名 as sysdba”,然后在sqlplus中alter user sys identified by 新密码;
alter user system identified by 新密码;
3.运行到C盘根目录
1)输入:SET ORACLE_SID = 你的SID名称
2)输入:sqlplus/nolog
3)输入:connect/as sysdba
4)输入:alert user sys identified by sys
5)输入:alert user system identified by system
6)更改完成,密码是Oracle数据库初始密码
4.首先,在CMD下输入SQLPLUS/NOLOG然后再在出来的界面中打入CONN/AS SYSDBA,这样就会以本地系统登录的用户为信任用户进入数据库的操作.解决这个问题的方式有两种,一个是:ALTER USER (USERNAME) IDENTIFIED BY “密码”;这个是可以改变USERNAME的密码.当然这个USERNAME必须已经存在的
另一种是:CREATE USER (USERNAME) IDENTIFIED BY “密码”;改变用户权限的命令是:GRANT ROLES TO (USERNAME);以上2种方法都是针对ORACLE 9I的版本 。
5.用orapwd.exe命令,可以修改命令。
orapwd file=’/oracle/pwdsid.ora’ password=123456这个命令是修改sys用户的密码。你把生成的文件覆盖原来的密码文件。除sys其他用户的密码不会改变。
6.su - oracle
sqlplus /nolog
conn / as sysdba
startup (如果数据库不是处于启动状态则启动)
alter user sys identified by 123456
然后就可以使用sys用户密码登陆了

操作系统认证方式
conn / as sysdba
alter user sys identified by xxx

posted @ 2014-05-02 13:16 鸿雁 阅读(124) | 评论 (0)编辑 收藏

oracle修改密码、添加用户及授权

  • 忘记密码处理

    登录:sqlplus/as sysdba;
    修改:alter user username identified by 密码;

  • 创建新用户

    create user 用户名 identified by 密码 default tablespace 表空间名

  • 用户授权

    Grant create session to SYSDATA;//授予SYSDATA连接数据的权限
    系统权限如下:
    Create session:连接数据库
    Create sequence:创建序列
    Create synonym:创建同名对象
    Create table:创建表
    Create any table:创建任何模式的表
    Drop table:删除表
    Create procedure:创建存储过程
    Execute any procedure:执行任何模式的存储过程
    Create user:创建用户
    Create view:创建视图
    Drop user:删除用户
    Drop any table:删除任何模式的表
    向用户授予系统特权

    Grant execute any procedure toSYSDATA with admin option
    此句意思为为sha用户创建系统特权并且可以用SYSDATA为别的用户授予权限

    连接SYSDATA用户connect SYSDATA/admin

    可以创建别的用户

    查看用户权限

    进入SYSDATA用户connect SYSDATA/admin

    Select * from user_sys_privs;可以查看到一个列表,
    列代表用户名权限是否可以给别的用户附加权限(N、Y)

    行代表权限都有那些

    撤销用户授予的权限

    必须连接connect system/admin

    撤销权限revoke是系统撤销权限的关键词

    Revoke execute any procedure from SYSDATA;
    更改用户密码

    进入SYSDATA用户connect SYSDATA/admin

    Password输入旧密码输入新密码俩边OK

    或者用alter user SYSDATA identified by xinmima

    删除用户

    Connect system/admin
    Drop user SYSDATA;
    为用户授予角色

    Grant dba to SYSDATA;
    Grant connect to SYSDATA;
    Linux下的plsql创建用户

    create user SYSDATA identified by admin
    Default tablespace user
    Temporary tablespace temp;
    Granr unlimited tablespace to SYSDATA;//权限
    Grant dba to SYSDATA;//角色
    Grant connect to SYSDATA;

    select * from dba_users; //用户表
    select * from dba_tablespaces; //表空间

    http://database.51cto.com/art/200911/160296.htm

    Oracle 权限设置
    一、权限分类:
    系统权限:系统规定用户使用数据库的权限。(系统权限是对用户而言)。
    实体权限:某种权限用户对其它用户的表或视图的存取权限。(是针对表或视图而言的)。

    二、系统权限管理:
    1、系统权限分类:
    DBA: 拥有全部特权,是系统最高权限,只有DBA才可以创建数据库结构。
    RESOURCE:拥有Resource权限的用户只可以创建实体,不可以创建数据库结构。
    CONNECT:拥有Connect权限的用户只可以登录Oracle,不可以创建实体,不可以创建数据库结构。
    对于普通用户:授予connect, resource权限。
    对于DBA管理用户:授予connect,resource, dba权限。

    2、系统权限授权命令:
    [系统权限只能由DBA用户授出:sys, system(最开始只能是这两个用户)]
    授权命令:SQL> grant connect, resource, dba to 用户名1 [,用户名2]…;
    [普通用户通过授权可以具有与system相同的用户权限,但永远不能达到与sys用户相同的权限,system用户的权限也可以被回收。]
    例:
    SQL> connect system/manager
    SQL> Create user user50 identified by user50;
    SQL> grant connect, resource to user50;
    查询用户拥有哪里权限:
    SQL> select * from dba_role_privs;
    SQL> select * from dba_sys_privs;
    SQL> select * from role_sys_privs;
    删除用户:SQL> drop user 用户名 cascade; //加上cascade则将用户连同其创建的东西全部删除

    3、系统权限传递:
    增加WITH ADMIN OPTION选项,则得到的权限可以传递。
    SQL> grant connect, resorce to user50 with admin option; //可以传递所获权限。

    4、系统权限回收:系统权限只能由DBA用户回收
    命令:SQL> Revoke connect, resource from user50;

    说明:
    1)如果使用WITH ADMIN OPTION为某个用户授予系统权限,那么对于被这个用户授予相同权限的所有用户来说,取消该用户的系统权限并不会级联取消这些用户的相同权限。
    2)系统权限无级联,即A授予B权限,B授予C权限,如果A收回B的权限,C的权限不受影响;系统权限可以跨用户回收,即A可以直接收回C用户的权限。

    三、实体权限管理
    1、实体权限分类:select, update, insert, alter, index, delete, all //all包括所有权限
    execute //执行存储过程权限
    user01:
    SQL> grant select, update, insert on product to user02;
    SQL> grant all on product to user02;
    user02:
    SQL> select * from user01.product;
    // 此时user02查user_tables,不包括user01.product这个表,但如果查all_tables则可以查到,因为他可以访问。

    2. 将表的操作权限授予全体用户:
    SQL> grant all on product to public; // public表示是所有的用户,这里的all权限不包括drop。
    [实体权限数据字典]:
    SQL> select owner, table_name from all_tables; // 用户可以查询的表
    SQL> select table_name from user_tables; // 用户创建的表
    SQL> select grantor, table_schema, table_name, privilege from all_tab_privs; // 获权可以存取的表(被授权的)
    SQL> select grantee, owner, table_name, privilege from user_tab_privs; // 授出权限的表(授出的权限)

    3. DBA用户可以操作全体用户的任意基表(无需授权,包括删除):
    DBA用户:
    SQL> Create table stud02.product(
    id number(10),
    name varchar2(20));
    SQL> drop table stud02.emp;
    SQL> create table stud02.employee
    as
    select * from scott.emp;

    4. 实体权限传递(with grant option):
    user01:
    SQL> grant select, update on product to user02 with grant option; // user02得到权限,并可以传递。

    5. 实体权限回收:
    user01:
    SQL>Revoke select, update on product from user02; //传递的权限将全部丢失。

    说明
    1)如果取消某个用户的对象权限,那么对于这个用户使用WITH GRANT OPTION授予权限的用户来说,同样还会取消这些用户的相同权限,也就是说取消授权时级联的。

    Oracle 用户管理
    一、创建用户的Profile文件
    SQL> create profile student limit // student为资源文件名
    FAILED_LOGIN_ATTEMPTS 3 //指定锁定用户的登录失败次数
    PASSWORD_LOCK_TIME 5 //指定用户被锁定天数
    PASSWORD_LIFE_TIME 30 //指定口令可用天数

    二、创建用户
    SQL> Create User username
    Identified by password
    Default Tablespace tablespace
    Temporary Tablespace tablespace
    Profile profile
    Quota integer/unlimited on tablespace;
    例:
    SQL> Create user acc01
    identified by acc01 // 如果密码是数字,请用双引号括起来
    default tablespace account
    temporary tablespace temp
    profile default
    quota 50m on account;
    SQL> grant connect, resource to acc01;
    [*] 查询用户缺省表空间、临时表空间
    SQL> select username, default_tablespace, temporary_tablespace from dba_users;
    [*] 查询系统资源文件名:
    SQL> select * from dba_profiles;
    资源文件类似表,一旦创建就会保存在数据库中。
    SQL> select username, profile, default_tablespace, temporary_tablespace from dba_users;
    SQL> create profile common limit
    failed_login_attempts 5
    idle_time 5;

    SQL> Alter user acc01 profile common;

    三、修改用户:
    SQL> Alter User 用户名
    Identified 口令
    Default Tablespace tablespace
    Temporary Tablespace tablespace
    Profile profile
    Quota integer/unlimited on tablespace;

    1、修改口令字:
    SQL>Alter user acc01 identified by “12345″;
    2、修改用户缺省表空间:
    SQL> Alter user acc01 default tablespace users;
    3、修改用户临时表空间
    SQL> Alter user acc01 temporary tablespace temp_data;
    4、强制用户修改口令字:
    SQL> Alter user acc01 password expire;
    5、将用户加锁
    SQL> Alter user acc01 account lock; // 加锁
    SQL> Alter user acc01 account unlock; // 解锁

    四、删除用户
    SQL>drop user 用户名; //用户没有建任何实体
    SQL> drop user 用户名 CASCADE; // 将用户及其所建实体全部删除
    *1. 当前正连接的用户不得删除。

    五、监视用户:
    1、查询用户会话信息:
    SQL> select username, sid, serial#, machine from v$session;
    2、删除用户会话信息:
    SQL> Alter system kill session ‘sid, serial#’;
    3、查询用户SQL语句:
    SQL> select user_name, sql_text from v$open_cursor;

    Oracle 角色管理
    一、何为角色
      角色。角色是一组权限的集合,将角色赋给一个用户,这个用户就拥有了这个角色中的所有权限。

    二、系统预定义角色
      预定义角色是在数据库安装后,系统自动创建的一些常用的角色。下介简单的介绍一下这些预定角色。角色所包含的权限可以用以下语句查询:
    sql>select * from role_sys_privs where role=’角色名’;

    1.CONNECT, RESOURCE, DBA
    这些预定义角色主要是为了向后兼容。其主要是用于数据库管理。oracle建议用户自己设计数据库管理和安全的权限规划,而不要简单的使用这些预定角色。将来的版本中这些角色可能不会作为预定义角色。

    2.DELETE_CATALOG_ROLE, EXECUTE_CATALOG_ROLE, SELECT_CATALOG_ROLE
    这些角色主要用于访问数据字典视图和包。

    3.EXP_FULL_DATABASE, IMP_FULL_DATABASE
    这两个角色用于数据导入导出工具的使用。

    4.AQ_USER_ROLE, AQ_ADMINISTRATOR_ROLE
    AQ:Advanced Query。这两个角色用于oracle高级查询功能。

    5. SNMPAGENT
    用于oracle enterprise manager和Intelligent Agent

    6.RECOVERY_CATALOG_OWNER
    用于创建拥有恢复库的用户。关于恢复库的信息,参考oracle文档《Oracle9i User-Managed Backup and Recovery Guide》

    7.HS_ADMIN_ROLE
    A DBA using Oracle’s heterogeneous services feature needs this role to access appropriate tables in the data dictionary.

    三、管理角色
    1.建一个角色
    sql>create role role1;

    2.授权给角色
    sql>grant create any table,create procedure to role1;

    3.授予角色给用户
    sql>grant role1 to user1;

    4.查看角色所包含的权限
    sql>select * from role_sys_privs;

    5.创建带有口令以角色(在生效带有口令的角色时必须提供口令)
    sql>create role role1 identified by password1;

    6.修改角色:是否需要口令
    sql>alter role role1 not identified;
    sql>alter role role1 identified by password1;

    7.设置当前用户要生效的角色
    (注:角色的生效是一个什么概念呢?假设用户a有b1,b2,b3三个角色,那么如果b1未生效,则b1所包含的权限对于a来讲是不拥有的,只有角色生效了,角色内的权限才作用于用户,最大可生效角色数由参数MAX_ENABLED_ROLES设定;在用户登录后,oracle将所有直接赋给用户的权限和用户默认角色中的权限赋给用户。)
    sql>set role role1;//使role1生效
    sql>set role role,role2;//使role1,role2生效
    sql>set role role1 identified by password1;//使用带有口令的role1生效
    sql>set role all;//使用该用户的所有角色生效
    sql>set role none;//设置所有角色失效
    sql>set role all except role1;//除role1外的该用户的所有其它角色生效。
    sql>select * from SESSION_ROLES;//查看当前用户的生效的角色。

    8.修改指定用户,设置其默认角色
    sql>alter user user1 default role role1;
    sql>alter user user1 default role all except role1;
    详见oracle参考文档

    9.删除角色
    sql>drop role role1;
    角色删除后,原来拥用该角色的用户就不再拥有该角色了,相应的权限也就没有了。

    说明:
    1)无法使用WITH GRANT OPTION为角色授予对象权限
    2)可以使用WITH ADMIN OPTION 为角色授予系统权限,取消时不是级联

  • posted @ 2014-05-02 11:05 鸿雁 阅读(183) | 评论 (0)编辑 收藏

    js合并指定列值重复的单元格

         function uniteTab(tableId,col) {
         //col-- 需要合并单元格的列 1开始
           var tb=document.getElementById(tableId);
           tb.style.display='';
            var i = 0;
            var j = 0;
            var rowCount = tb.rows.length; //   行数 
            var colCount = tb.rows[0].cells.length; //   列数 
            var obj1 = null;
            var obj2 = null;
            //为每个单元格命名 
            for (i = 0; i < rowCount; i++) {
                for (j = 0; j < colCount; j++) {
                    tb.rows[i].cells[j].id = "tb__" + i.toString() + "_" + j.toString();
                }
            }
            //合并行 
            for (i = 0; i < colCount; i++) {
                if (i == colLength) break;
                obj1 = document.getElementById("tb__0_" + i.toString())
                for (j = 1; j < rowCount; j++) {
                    obj2 = document.getElementById("tb__" + j.toString() + "_" + i.toString());
                    if (obj1.innerText == obj2.innerText) {
                        obj1.rowSpan++;
                        obj2.parentNode.removeChild(obj2);
                    } else {
                        obj1 = document.getElementById("tb__" + j.toString() + "_" + i.toString());
                    }
                }
            }
            //合并列
            for (i = 0; i < rowCount; i++) {
                colCount = tb.rows[i].cells.length;
                obj1 = document.getElementById(tb.rows[i].cells[0].id);
                for (j = 1; j < colCount; j++) {
                    if (j >= colLength) break;
                    if (obj1.colSpan >= colLength) break;

                    obj2 = document.getElementById(tb.rows[i].cells[j].id);
                    if (obj1.innerText == obj2.innerText) {
                        obj1.colSpan++;
                        obj2.parentNode.removeChild(obj2);
                        j = j - 1;
                    }
                    else {
                        obj1 = obj2;
                        j = j + obj1.rowSpan;
                    }
                }
            }
        }

    posted @ 2013-07-06 15:09 鸿雁 阅读(882) | 评论 (0)编辑 收藏

    Oracle数据库分区表操作方法(4)

    1.3. 分区表索引的使用:

       分区表和一般表一样可以建立索引,分区表可以创建局部索引和全局索引。当分区中出现许多事务并且要保证所有分区中的数据记录的唯一性时采用全局索引。

       1.3.1. 局部索引分区的建立:
    以下为引用的内容:
    SQL> create index dinya_idx_t on dinya_test(item_id)
    2 local
    3 (
    4 partition idx_1 tablespace dinya_space01,
    5 partition idx_2 tablespace dinya_space02,
    6 partition idx_3 tablespace dinya_space03
    7 );
    Index created.
    SQL>

       看查询的执行计划,从下面的执行计划可以看出,系统已经使用了索引:

    以下为引用的内容:
    SQL> select * from dinya_test partition(part_01) t where t.item_id=12;
    Execution Plan
    ----------------------------------------------------------
    0 SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=187)
    1 0 TABLE ACCESS (BY LOCAL INDEX ROWID) OF ’DINYA_TEST’ (Cost=
    2 Card=1 Bytes=187)
    2 1 INDEX (RANGE SCAN) OF ’DINYA_IDX_T’ (NON-UNIQUE) (Cost=1
    Card=1)
    Statistics
    ----------------------------------------------------------
    0 recursive calls
    0 db block gets
    4 consistent gets
    0 physical reads
    0 redo size
    334 bytes sent via SQL*Net to client
    309 bytes received via SQL*Net from client
    2 SQL*Net roundtrips to/from client

    1 sorts (memory)
    0 sorts (disk)
    2 rows processed
    SQL>

       1.3.2. 全局索引分区的建立

       全局索引建立时global 子句允许指定索引的范围值,这个范围值为索引字段的范围值:

    以下为引用的内容:
    SQL> create index dinya_idx_t on dinya_test(item_id)
    2 global partition by range(item_id)
    3 (
    4 partition idx_1 values less than (1000) tablespace dinya_space01,
    5 partition idx_2 values less than (10000) tablespace dinya_space02,
    6 partition idx_3 values less than (maxvalue) tablespace dinya_space03
    7 );
    Index created.
    SQL>

       本例中对表的item_id字段建立索引分区,当然也可以不指定索引分区名直接对整个表建立索引,如:

    以下为引用的内容:
    SQL> create index dinya_idx_t on dinya_test(item_id);
    Index created.
    SQL>

       同样的,对全局索引根据执行计划可以看出索引已经可以使用:

    以下为引用的内容:
    SQL> select * from dinya_test t where t.item_id=12;
    Execution Plan
    ----------------------------------------------------------
    0 SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=3 Bytes=561)
    1 0 TABLE ACCESS (BY GLOBAL INDEX ROWID) OF ’DINYA_TEST’ (Cost
    =2 Card=3 Bytes=561)
    2 1 INDEX (RANGE SCAN) OF ’DINYA_IDX_T’ (NON-UNIQUE) (Cost=1
    Card=3)
    Statistics
    ----------------------------------------------------------
    5 recursive calls
    0 db block gets
    10 consistent gets
    0 physical reads

    0 redo size
    420 bytes sent via SQL*Net to client
    309 bytes received via SQL*Net from client
    2 SQL*Net roundtrips to/from client
    3 sorts (memory)
    0 sorts (disk)
    5 rows processed
    SQL>

    posted @ 2011-12-12 22:57 鸿雁 阅读(304) | 评论 (0)编辑 收藏

    Oracle数据库分区表操作方法(5)

    1.4. 分区表的维护:

       了解了分区表的建立、索引的建立、表和索引的使用后,在应用的还要经常对分区进行维护和管理。日常维护和管理的内容包括:增加一个分区,合并一个分区及删除分区等等。下面以范围分区为例说明增加、合并、删除分区的一般操作:

       1.4.1. 增加一个分区:
    以下为引用的内容:
    SQL> alter table dinya_test
    2 add partition part_04 values less than(to_date(’2012-01-01’,’yyyy-mm-dd’))
    tablespace dinya_spa
    ce03;
    Table altered.
    SQL>

       增加一个分区的时候,增加的分区的条件必须大于现有分区的最大值,否则系统将提示ORA-14074 partition bound must collate higher than that of the last partition 错误。

       1.4.2. 合并一个分区:
    以下为引用的内容:
    SQL> alter table dinya_test merge partitions part_01,part_02 into partition part_02;
    Table altered.
    SQL>

       在本例中将原有的表的part_01分区和part_02分区进行了合并,合并后的分区为part_02,如果在合并的时候把合并后的分区定为part_01的时候,系统将提示ORA-14275 cannot reuse lower-bound partition as resulting partition 错误。

       1.4.3. 删除分区:
    以下为引用的内容:
    SQL> alter table dinya_test drop partition part_01;
    Table altered.
    SQL>

       删除分区表的一个分区后,查询该表的数据时显示,该分区中的数据已全部丢失,所以执行删除分区动作时要慎重,确保先备份数据后再执行,或将分区合并。

      1.5. 总结:

       需要说明的是,本文在举例说名分区表事务操作的时候,都指定了分区,因为指定了分区,系统在执行的时候则只操作该分区的记录,提高了数据处理的速度。不要指定分区直接操作数据也是可以的。在分区表上建索引及多索引的使用和非分区表一样。此外,因为在维护分区的时候可能对分区的索引会产生一定的影响,可能需要在维护之后重建索引,相关内容请参考分区表索引部分的文档

    posted @ 2011-12-12 22:57 鸿雁 阅读(295) | 评论 (0)编辑 收藏

    Oracle数据库分区表操作方法(3)

    1.2. 分区表操作

       以上了解了三种分区表的建表方法,下面将使用实际的数据并针对按日期的范围分区来测试分区表的数据记录的操作。

       1.2.1. 插入记录:
    以下为引用的内容:
    SQL> insert into dinya_test values(1,12,’BOOKS’,sysdate);
    1 row created.
    SQL> insert into dinya_test values(2,12, ’BOOKS’,sysdate+30);
    1 row created.

    SQL> insert into dinya_test values(3,12, ’BOOKS’,to_date(’2006-05-30’,’yyyy-mm-dd’));
    1 row created.
    SQL> insert into dinya_test values(4,12, ’BOOKS’,to_date(’2007-06-23’,’yyyy-mm-dd’));
    1 row created.
    SQL> insert into dinya_test values(5,12, ’BOOKS’,to_date(’2011-02-26’,’yyyy-mm-dd’));
    1 row created.
    SQL> insert into dinya_test values(6,12, ’BOOKS’,to_date(’2011-04-30’,’yyyy-mm-dd’));
    1 row created.
    SQL> commit;
    Commit complete.
    SQL>
       按上面的建表结果,2006年前的数据将存储在第一个分区part_01上,而2006年到2010年的交易数据将存储在第二个分区part_02上,2010年以后的记录存储在第三个分区part_03上。


       1.2.2. 查询分区表记录:
    以下为引用的内容:
    SQL> select * from dinya_test partition(part_01);
    TRANSACTION_ID ITEM_ID ITEM_DESCRIPTION TRANSACTION_DATE
    --------------------------------------------------------------------------------
    1 12 BOOKS 2005-1-14 14:19:
    2 12 BOOKS 2005-2-13 14:19:
    SQL>
    SQL> select * from dinya_test partition(part_02);
    TRANSACTION_ID ITEM_ID ITEM_DESCRIPTION TRANSACTION_DATE
    --------------------------------------------------------------------------------
    3 12 BOOKS 2006-5-30
    4 12 BOOKS 2007-6-23
    SQL>
    SQL> select * from dinya_test partition(part_03);
    TRANSACTION_ID ITEM_ID ITEM_DESCRIPTION TRANSACTION_DATE
    --------------------------------------------------------------------------------
    5 12 BOOKS 2011-2-26
    6 12 BOOKS 2011-4-30
    SQL>

       从查询的结果可以看出,插入的数据已经根据交易时间范围存储在不同的分区中。这里是指定了分区的查询,当然也可以不指定分区,直接执行select * from dinya_test查询全部记录。

       在也检索的数据量很大的时候,指定分区会大大提高检索速度。

       1.2.3. 更新分区表的记录:
    以下为引用的内容:
    SQL> update dinya_test partition(part_01) t set t.item_description=’DESK’ where
    t.transaction_id=1;
    1 row updated.
    SQL> commit;
    Commit complete.
    SQL>

       这里将第一个分区中的交易ID=1的记录中的item_description字段更新为“DESK”,可以看到已经成功更新了一条记录。但是当更新的时候指定了分区,而根据查询的记录不在该分区中时,将不会更新数据,请看下面的例子:
    以下为引用的内容:
    SQL> update dinya_test partition(part_01) t set t.item_description=’DESK’ where
    t.transaction_id=6;
    0 rows updated.
    SQL> commit;
    Commit complete.
    SQL>

       指定了在第一个分区中更新记录,但是条件中限制交易ID为6,而查询全表,交易ID为6的记录在第三个分区中,这样该条语句将不会更新记录。

       1.2.4. 删除分区表记录:
    以下为引用的内容:
    SQL> delete from dinya_test partition(part_02) t where t.transaction_id=4;
    1 row deleted.
    SQL> commit;
    Commit complete.
    SQL>


       上面例子删除了第二个分区part_02中的交易记录ID为4的一条记录,和更新数据相同,如果指定了分区,而条件中的数据又不在该分区中时,将不会删除任何数据。

    posted @ 2011-12-12 22:56 鸿雁 阅读(497) | 评论 (0)编辑 收藏

    Oracle数据库分区表操作方法(一)

           摘要:在大量业务数据处理的项目中,可以考虑使用分区表来提高应用系统的性能并方便数据管理,本文详细介绍了分区表的使用。

       在大型的企业应用或企业级的数据库应用中,要处理的数据量通常可以达到几十到几百GB,有的甚至可以到TB级。虽然存储介质和数据处理技术的发展也很快,但是仍然不能满足用户的需求,为了使用户的大量的数据在读写操作和查询中速度更快,Oracle提供了对表和索引进行分区的技术,以改善大型应用系统的性能。

       使用分区的优点:

       ·增强可用性:如果表的某个分区出现故障,表在其他分区的数据仍然可用;

       ·维护方便:如果表的某个分区出现故障,需要修复数据,只修复该分区即可;

       ·均衡I/O:可以把不同的分区映射到磁盘以平衡I/O,改善整个系统性能;

       ·改善查询性能:对分区对象的查询可以仅搜索自己关心的分区,提高检索速度。

       Oracle数据库提供对表或索引的分区方法有三种:

       ·范围分区

       ·Hash分区(散列分区)

       ·复合分区

       下面将以实例的方式分别对这三种分区方法来说明分区表的使用。为了测试方便,我们先建三个表空间。
    以下为引用的内容:
    create tablespace dinya_space01
    datafile ’/test/demo/oracle/demodata/dinya01.dnf’ size 50M
    create tablespace dinya_space01
    datafile ’/test/demo/oracle/demodata/dinya02.dnf’ size 50M
    create tablespace dinya_space01
    datafile ’/test/demo/oracle/demodata/dinya03.dnf’ size 50M

      1.1. 分区表的创建

       1.1.1. 范围分区

       范围分区就是对数据表中的某个值的范围进行分区,根据某个值的范围,决定将该数据存储在哪个分区上。如根据序号分区,根据业务记录的创建日期进行分区等。

       需求描述:有一个物料交易表,表名:material_transactions。该表将来可能有千万级的数据记录数。要求在建该表的时候使用分区表。这时候我们可以使用序号分区三个区,每个区中预计存储三千万的数据,也可以使用日期分区,如每五年的数据存储在一个分区上。

       根据交易记录的序号分区建表:
    以下为引用的内容:
    SQL> create table dinya_test
    2 (
    3 transaction_id number primary key,
    4 item_id number(8) not null,
    5 item_description varchar2(300),
    6 transaction_date date not null
    7 )
    8 partition by range (transaction_id)
    9 (
    10 partition part_01 values less than(30000000) tablespace dinya_space01,
    11 partition part_02 values less than(60000000) tablespace dinya_space02,
    12 partition part_03 values less than(maxvalue) tablespace dinya_space03
    13 );
    Table created.
        建表成功,根据交易的序号,交易ID在三千万以下的记录将存储在第一个表空间dinya_space01中,分区名为:par_01,在三千万到六千万之间的记录存储在第二个表空间:

       dinya_space02中,分区名为:par_02,而交易ID在六千万以上的记录存储在第三个表空间dinya_space03中,分区名为par_03.

       根据交易日期分区建表:
    以下为引用的内容:
    SQL> create table dinya_test
    2 (
    3 transaction_id number primary key,
    4 item_id number(8) not null,

    5 item_description varchar2(300),
    6 transaction_date date not null
    7 )
    8 partition by range (transaction_date)
    9 (
    10 partition part_01 values less than(to_date(’2006-01-01’,’yyyy-mm-dd’))
    tablespace dinya_space01,
    11 partition part_02 values less than(to_date(’2010-01-01’,’yyyy-mm-dd’))
    tablespace dinya_space02,
    12 partition part_03 values less than(maxvalue) tablespace dinya_space03
    13 );
    Table created.


       这样我们就分别建了以交易序号和交易日期来分区的分区表。每次插入数据的时候,系统将根据指定的字段的值来自动将记录存储到制定的分区(表空间)中。

       当然,我们还可以根据需求,使用两个字段的范围分布来分区,如partition by range ( transaction_id ,transaction_date), 分区条件中的值也做相应的改变,请读者自行测试。

    posted @ 2011-12-12 22:55 鸿雁 阅读(270) | 评论 (0)编辑 收藏

    Oracle数据库分区表操作方法(二)

    1.1.2. Hash分区(散列分区)

       散列分区为通过指定分区编号来均匀分布数据的一种分区类型,因为通过在I/O设备上进行散列分区,使得这些分区大小一致。如将物料交易表的数据根据交易ID散列地存放在指定的三个表空间中:

    以下为引用的内容:
    SQL> create table dinya_test
    2 (
    3 transaction_id number primary key,
    4 item_id number(8) not null,
    5 item_description varchar2(300),
    6 transaction_date date
    7 )
    8 partition by hash(transaction_id)
    9 (
    10 partition part_01 tablespace dinya_space01,
    11 partition part_02 tablespace dinya_space02,
    12 partition part_03 tablespace dinya_space03
    13 );
    Table created.

       建表成功,此时插入数据,系统将按transaction_id将记录散列地插入三个分区中,这里也就是三个不同的表空间中。

       1.1.3. 复合分区

       有时候我们需要根据范围分区后,每个分区内的数据再散列地分布在几个表空间中,这样我们就要使用复合分区。复合分区是先使用范围分区,然后在每个分区内再使用散列分区的一种分区方法,如将物料交易的记录按时间分区,然后每个分区中的数据分三个子分区,将数据散列地存储在三个指定的表空间中:

    以下为引用的内容:
    SQL> create table dinya_test
    2 (
    3 transaction_id number primary key,
    4 item_id number(8) not null,
    5 item_description varchar2(300),
    6 transaction_date date
    7 )
    8 partition by range(transaction_date)subpartition by hash(transaction_id)
    9 subpartitions 3 store in (dinya_space01,dinya_space02,dinya_space03)
    10 (
    11 partition part_01 values less than(to_date(’2006-01-01’,’yyyy-mm-dd’)),
    12 partition part_02 values less than(to_date(’2010-01-01’,’yyyy-mm-dd’)),
    13 partition part_03 values less than(maxvalue)
    14 );
    Table created.


       该例中,先是根据交易日期进行范围分区,然后根据交易的ID将记录散列地存储在三个表空间中。

    posted @ 2011-12-12 22:55 鸿雁 阅读(226) | 评论 (0)编辑 收藏

    Hibernate继承映射(Inheritance Mappings)

    三种策略

    Hibernate支持三种基本的继承映射策略:

    • 每个类分层结构一张表(table per class hierarchy)

    • 每个子类一张表(table per subclass)

    • 每个具体类一张表(table per concrete class)

    此外,Hibernate还支持第四种稍有不同的多态映射策略:

    • 隐式多态(implicit polymorphism)

    对于同一个继承层次内的不同分支,可以采用不同的映射策略,然后用隐式多 态来完成跨越整个层次的多态。但是在同一个<class>根元素 下,Hibernate不支持混合了元素<subclass><joined-subclass><union-subclass> 的映射。在同一个<class>元素下,可以混合使用 “每个类分层结构一张表”(table per hierarchy) 和“每个子类一张表”(table per subclass) 这两种映射策略,这是通过结合元素<subclass><join>来实现的(见后)。

    在多个映射文件中,可以直接在hibernate-mapping根下定义subclassunion-subclassjoined-subclass。也就是说,你可以仅加入一个新的映射文件来扩展类层次。你必须在subclass的映射中指明extends属性,给出一个之前定义的超类的名字。注意,在以前,这一功能对映射文件的顺序有严格的要求,从Hibernate 3开始,使用extends关键字的时侯,对映射文件的顺序不再有要求;但在每个映射文件里,超类必须在子类之前定义。

     <hibernate-mapping>
         <subclass name="DomesticCat" extends="Cat" discriminator-value="D">
              <property name="name" type="string"/>
         </subclass>
     </hibernate-mapping>

    9.1.1. 每个类分层结构一张表(Table per class hierarchy)

    假设我们有接口Payment和它的几个实现类: CreditCardPayment, CashPayment, 和ChequePayment。则“每个类分层结构一张表”(Table per class hierarchy)的映射代码如下所示:

    <class name="Payment" table="PAYMENT">
        <id name="id" type="long" column="PAYMENT_ID">
            <generator class="native"/>
        </id>
        <discriminator column="PAYMENT_TYPE" type="string"/>
        <property name="amount" column="AMOUNT"/>
        ...
        <subclass name="CreditCardPayment" discriminator-value="CREDIT">
            <property name="creditCardType" column="CCTYPE"/>
            ...
        </subclass>
        <subclass name="CashPayment" discriminator-value="CASH">
            ...
        </subclass>
        <subclass name="ChequePayment" discriminator-value="CHEQUE">
            ...
        </subclass>
    </class>

    采用这种策略只需要一张表即可。它有一个很大的限制:要求那些由子类定义的字段, 如CCTYPE,不能有非空(NOT NULL)约束。

    9.1.2. 每个子类一张表(Table per subclass)

    对于上例中的几个类而言,采用“每个子类一张表”的映射策略,代码如下所示:

    <class name="Payment" table="PAYMENT">
        <id name="id" type="long" column="PAYMENT_ID">
            <generator class="native"/>
        </id>
        <property name="amount" column="AMOUNT"/>
        ...
        <joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
            <key column="PAYMENT_ID"/>
            ...
        </joined-subclass>
        <joined-subclass name="CashPayment" table="CASH_PAYMENT">
            <key column="PAYMENT_ID"/>
            <property name="creditCardType" column="CCTYPE"/>
            ...
        </joined-subclass>
        <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
            <key column="PAYMENT_ID"/>
            ...
        </joined-subclass>
    </class>

    需要四张表。三个子类表通过主键关联到超类表(因而关系模型实际上是一对一关联)。

    9.1.3. 每个子类一张表(Table per subclass),使用辨别标志(Discriminator)

    注意,对“每个子类一张表”的映射策略,Hibernate的实现不需要辨别字段,而其他 的对象/关系映射工具使用了一种不同于Hibernate的实现方法,该方法要求在超类 表中有一个类型辨别字段(type discriminator column)。Hibernate采用的方法更 难实现,但从关系(数据库)的角度来看,按理说它更正确。若你愿意使用带有辨别字 段的“每个子类一张表”的策略,你可以结合使用<subclass><join>,如下所示:

    <class name="Payment" table="PAYMENT">
        <id name="id" type="long" column="PAYMENT_ID">
            <generator class="native"/>
        </id>
        <discriminator column="PAYMENT_TYPE" type="string"/>
        <property name="amount" column="AMOUNT"/>
        ...
        <subclass name="CreditCardPayment" discriminator-value="CREDIT">
            <join table="CREDIT_PAYMENT">
                <key column="PAYMENT_ID"/>
                <property name="creditCardType" column="CCTYPE"/>
                ...
            </join>
        </subclass>
        <subclass name="CashPayment" discriminator-value="CASH">
            <join table="CASH_PAYMENT">
                <key column="PAYMENT_ID"/>
                ...
            </join>
        </subclass>
        <subclass name="ChequePayment" discriminator-value="CHEQUE">
            <join table="CHEQUE_PAYMENT" fetch="select">
                <key column="PAYMENT_ID"/>
                ...
            </join>
        </subclass>
    </class>

    可选的声明fetch="select",是用来告诉Hibernate,在查询超类时, 不要使用外部连接(outer join)来抓取子类ChequePayment的数据。

    9.1.4. 混合使用“每个类分层结构一张表”和“每个子类一张表”

    你甚至可以采取如下方法混和使用“每个类分层结构一张表”和“每个子类一张表”这两种策略:

    <class name="Payment" table="PAYMENT">
        <id name="id" type="long" column="PAYMENT_ID">
            <generator class="native"/>
        </id>
        <discriminator column="PAYMENT_TYPE" type="string"/>
        <property name="amount" column="AMOUNT"/>
        ...
        <subclass name="CreditCardPayment" discriminator-value="CREDIT">
            <join table="CREDIT_PAYMENT">
                <property name="creditCardType" column="CCTYPE"/>
                ...
            </join>
        </subclass>
        <subclass name="CashPayment" discriminator-value="CASH">
            ...
        </subclass>
        <subclass name="ChequePayment" discriminator-value="CHEQUE">
            ...
        </subclass>
    </class>

    对上述任何一种映射策略而言,指向根类Payment的 关联是使用<many-to-one>进行映射的。

    <many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>

    9.1.5. 每个具体类一张表(Table per concrete class)

    对于“每个具体类一张表”的映射策略,可以采用两种方法。第一种方法是使用 <union-subclass>

    <class name="Payment">
        <id name="id" type="long" column="PAYMENT_ID">
            <generator class="sequence"/>
        </id>
        <property name="amount" column="AMOUNT"/>
        ...
        <union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
            <property name="creditCardType" column="CCTYPE"/>
            ...
        </union-subclass>
        <union-subclass name="CashPayment" table="CASH_PAYMENT">
            ...
        </union-subclass>
        <union-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
            ...
        </union-subclass>
    </class>

    这里涉及三张与子类相关的表。每张表为对应类的所有属性(包括从超类继承的属性)定义相应字段。

    这种方式的局限在于,如果一个属性在超类中做了映射,其字段名必须与所有子类 表中定义的相同。(我们可能会在Hibernate的后续发布版本中放宽此限制。) 不允许在联合子类(union subclass)的继承层次中使用标识生成器策略(identity generator strategy), 实际上, 主键的种子(primary key seed)不得不为同一继承层次中的全部被联合子类所共用.

    假若超类是抽象类,请使用abstract="true"。当然,假若它不是抽象的,需要一个额外的表(上面的例子中,默认是PAYMENT),来保存超类的实例。

    9.1.6. Table per concrete class, using implicit polymorphism

    9.1.6. Table per concrete class, using implicit polymorphism

    另一种可供选择的方法是采用隐式多态:

    <class name="CreditCardPayment" table="CREDIT_PAYMENT">
        <id name="id" type="long" column="CREDIT_PAYMENT_ID">
            <generator class="native"/>
        </id>
        <property name="amount" column="CREDIT_AMOUNT"/>
        ...
    </class>
    
    <class name="CashPayment" table="CASH_PAYMENT">
        <id name="id" type="long" column="CASH_PAYMENT_ID">
            <generator class="native"/>
        </id>
        <property name="amount" column="CASH_AMOUNT"/>
        ...
    </class>
    
    <class name="ChequePayment" table="CHEQUE_PAYMENT">
        <id name="id" type="long" column="CHEQUE_PAYMENT_ID">
            <generator class="native"/>
        </id>
        <property name="amount" column="CHEQUE_AMOUNT"/>
        ...
    </class>

    注意,我们没有在任何地方明确的提及接口Payment。同时注意 Payment的属性在每个子类中都进行了映射。如果你想避免重复, 可以考虑使用XML实体(例如:位于DOCTYPE声明内的 [ <!ENTITY allproperties SYSTEM "allproperties.xml"> ] 和映射中的&allproperties;)。

    这种方法的缺陷在于,在Hibernate执行多态查询时(polymorphic queries)无法生成带 UNION的SQL语句。

    对于这种映射策略而言,通常用<any>来实现到 Payment的多态关联映射。

    <any name="payment" meta-type="string" id-type="long">
        <meta-value value="CREDIT" class="CreditCardPayment"/>
        <meta-value value="CASH" class="CashPayment"/>
        <meta-value value="CHEQUE" class="ChequePayment"/>
        <column name="PAYMENT_CLASS"/>
        <column name="PAYMENT_ID"/>
    </any>

    9.1.7. 隐式多态和其他继承映射混合使用

    对这一映射还有一点需要注意。因为每个子类都在各自独立的元素<class> 中映射(并且Payment只是一个接口),每个子类可以很容易的成为另一 个继承体系中的一部分!(你仍然可以对接口Payment使用多态查询。)

    <class name="CreditCardPayment" table="CREDIT_PAYMENT">
        <id name="id" type="long" column="CREDIT_PAYMENT_ID">
            <generator class="native"/>
        </id>
        <discriminator column="CREDIT_CARD" type="string"/>
        <property name="amount" column="CREDIT_AMOUNT"/>
        ...
        <subclass name="MasterCardPayment" discriminator-value="MDC"/>
        <subclass name="VisaPayment" discriminator-value="VISA"/>
    </class>
    
    <class name="NonelectronicTransaction" table="NONELECTRONIC_TXN">
        <id name="id" type="long" column="TXN_ID">
            <generator class="native"/>
        </id>
        ...
        <joined-subclass name="CashPayment" table="CASH_PAYMENT">
            <key column="PAYMENT_ID"/>
            <property name="amount" column="CASH_AMOUNT"/>
            ...
        </joined-subclass>
        <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
            <key column="PAYMENT_ID"/>
            <property name="amount" column="CHEQUE_AMOUNT"/>
            ...
        </joined-subclass>
    </class>

    我们还是没有明确的提到Payment。 如果我们针对接口Payment执行查询 ——如from Payment—— Hibernate 自动返回CreditCardPayment(和它的子类,因为 它们也实现了接口Payment)、 CashPaymentChequepayment的实例, 但不返回NonelectronicTransaction的实例。

    posted @ 2011-11-26 23:52 鸿雁 阅读(801) | 评论 (0)编辑 收藏

    JVM优化配置

     

    JVM优化配置《一》

    OOM这个缩写就是Java程序开发过程中让人最头痛的问题:Out of Memory。在很多开发人员的开发过程中,或多或少的都会遇到这类问题,这类问题定位比较困难,往往需要根据经验来判断可能出现问题的代码。原因主要是 两个:对象没有被释放(多种情况引起,往往是比较隐蔽的引用导致被Hold而无法被回收)。另一种就是真的Memory不够用了,需要增加JVM的 Heap来满足应用程序的需求。最近有同事发的关于解决OOM的问题,让我了解了原来OOM除了在JVM Heap不够时会发生,在Native Heap不够的时候也会发生,同时JVM Heap和Native Heap存在着相互影响和平衡的关系,因此就仔细的去看了关于OOM和JVM配置优化的内容。
    OOM
           在 其他语言类似于C,Delphi等等由于内存都是由自己分配和管理,因此内存泄露的问题比较常见,同时也是很头痛的一件事情。而Java的对象生命周期管 理都是JVM来做的,简化了开发人员的非业务逻辑的处理,但是这种自动管理回收机制也是基于一些规则的,而违背了这些规则的时候,就会造成所谓的 “Memory Leak”。
    OOM(Java Heap)
           错误提示:java.lang.OutOfMemoryError。
    这 类OOM是由于JVM分配的给应用的Heap Memory已经被耗尽,可能是因为应用在高负荷的情况下的却需要很大的内存,因此可以通过修改JVM参数来增加Java Heap Memory(不过也不能无限制增加,后面那种OOM有可能就是因为这个原因而产生)。另一种情况是因为应用程序使用对象或者资源没有释放,导致内存消耗 持续增加,最后出现OOM,这类问题引起的原因往往是应用已不需要的对象还被其他有效对象所引用,那么就无法释放,可能是业务代码逻辑造成的(异常处理不 够例如IO等资源),也可能是对于第三方开源项目中资源释放了解不够导致使用以后资源没有释放(例如JDBC的ResultSet等)。
           几个容易出现问题的场景:
           1.应用的缓存或者Collection:如果应用要缓存Java对象或者是在一个Collection中保存对象,那么就要确定是否会有大量的对象存入,要做保护,以防止在大数据量下大量内存被消耗,同时要保证Cache的大小不会无限制增加。
           2.生命周期较长的对象:尽量简短对象的生命周期,现在采用对象的创建释放代价已经很低,同时作了很好的优化,要比创建一个对象长期反复使用要好。如果能够设置超时的情景下,尽量设置超时。
           3.类似于JDBC的Connection Pool,在使用Pool中的对象以后需要释放并返回,不然就会造成Pool的不断增大,在其他Pool中使用也是一样。同样ResultSet,IO这类资源的释放都需要注意。
           解决的方法就是查找错误或者是增加Java Heap Memory。对于此类问题检测工具相当多,这里就不做介绍了。      
    OOM(Native Heap)
    错误提示:requested XXXX bytes for ChunkPool::allocate. Out of swap space。
           Native Heap Memory是JVM 内部使用的Memory,这部分的Memory可以通过JDK提供的JNI的方式去访问,这部分Memory效率很高,但是管理需要自己去做,如果没有把 握最好不要使用,以防出现内存泄露问题。JVM 使用Native Heap Memory用来优化代码载入(JTI代码生成),临时对象空间申请,以及JVM内部的一些操作。这次同事在压力测试中遇到的问题就是这类OOM,也就是 这类Memory耗尽。同样这类OOM产生的问题也是分成正常使用耗尽和无释放资源耗尽两类。无释放资源耗尽很多时候不是程序员自身的原因,可能是引用的 第三方包的缺陷,例如很多人遇到的Oracle 9 JDBC驱动在低版本中有内存泄露的问题。要确定这类问题,就需要去观察Native Heap Memory的增长和使用情况,在服务器应用起来以后,运行一段时间后JVM对于Native Heap Memory的使用会达到一个稳定的阶段,此时可以看看什么操作对于Native Heap Memory操作频繁,而且使得Native Heap Memory增长,对于Native Heap Memory的情况我还没有找到办法去检测,现在能够看到的就是为JVM启动时候增加-verbose:jni参数来观察对于Native Heap Memory的操作。另一种情况就是正常消耗Native Heap Memory,对于Native Heap Memory的使用主要取决于JVM代码生成,线程创建,用于优化的临时代码和对象产生。当正常耗尽Native Heap Memory时,那么就需要增加Native Heap Memory,此时就会和我们前面提到增加java Heap Memory的情况出现矛盾。
    应用内存组合
           对 于应用来说,可分配的内存受到OS的限制,不同的OS对进程所能访问虚拟内存地址区间直接影响对于应用内存的分配,32位的操作系统通常最大支持4G的内 存寻址,而Linux一般为3G,Windows为2G。然而这些大小的内存并不会全部给JVM的Java Heap使用,它主要会分成三部分:Java Heap,Native Heap,载入资源和类库等所占用的内存。那么由此可见,Native Heap和 Java Heap大小配置是相互制约的,哪一部分分配多了都可能会影响到另外一部分的正常工作,因此如果通过命令行去配置,那么需要确切的了解应用使用情况,否则 采用默认配置自动监测会更好的优化应用使用情况。
           同样要注意的就是进程的虚拟内存和机器的实际内存还是有区别的,对于机器来说实际内存以及硬盘提供的虚拟内存都是提供给机器上所有进程使用的,因此在设置JVM参数时,它的虚拟内存绝对不应该超过实际内存的大小。
    《二》
           这 里首先要说明的是这里提到的JVM是Sun的HotSpot JVM 5和以上的版本。性能优化在应用方面可以有很多手段,包括Cache,多线程,各种算法等等。通常情况下是不建议在没有任何统计和分析的情况下去手动配置 JVM的参数来调整性能,因为在JVM 5以上已经作了根据机器和OS的情况自动配置合适参数的算法,基本能够满足大部分的情况,当然这种自动适配只是一种通用的方式,如果说真的要达到最优,那 么还是需要根据实际的使用情况来手动的配置各种参数设置,提高性能。
           JVM能够对性能产生影响的最大部分就是对于内存的管理。从jdk 1.5以后内存管理和分配有了很多的改善和提高。
    内存分配以及管理的几个基本概念和参数说明:
    Java Hotspot Mode:
    server 和 client两种模式,如果不配置,JVM会根据应用服务器硬件配置自动选择模式,server模式启动比较慢,但是运行期速度得到了优化,client启动比较快,但是运行期响应没有server模式的优化,适合于个人PC的服务开发和测试。
    Garbage Collector Policy:
           在Jdk 1.5的时候已经提供了三种GC,除了原来提供的串行GC(SerialGC)以外,还提供了两种新的GC:ParallelGC和 ConcMarkSweepGC。ParallelGC采用了多线程并行管理和回收垃圾对象,提高了回收效率,提高了服务器的吞吐量,适合于多处理器的服 务器。ConcMarkSweepGC采用的是并发方式来管理和回收垃圾对象,降低垃圾回收产生的响应暂停时间。这里说一下并发和并行的区别,并发指的是 多个进程并行执行垃圾回收,那么可以很好的利用多处理器,而并行指的是应用程序不需要暂停可以和垃圾回收线程并发工作。串行GC适合小型应用和单处理器系 统(无需多线程交互,效率比较高),后两者适合大型系统。
           使用方式就是在参数配置中增加-XX:+UseParallelGC等方式来设置。
           对于这部分的配置在网上有很多的实例可以参考,不过最终采用哪一种GC还是要根据具体的情况来分析和选择。
    Heap:
           OOM的 各种经历已经让每一个架构师开发人员看到了了解Heap的重要性。OOM已经是Heap的临界点,不得不引起注意,然而Heap对于性能的潜在影响并未被 引起重视,不过和GC配置一样,在没有对使用情况作仔细分析和研究的情况下,贸然的去修改Heap配置,可能适得其反,这里就来看一下Heap的一些概念 和对于性能的影响。
           我们的应用所能够得到的最大的Heap受三部分因素的制约:数据处理 模型(32位或者64位操作系统),系统地虚拟内存总数和系统的物理内存总数。首先Heap的大小不能超过不同操作系统的进程寻址范围,当前大部分系统最 高限度是4G,Windows通常是2G,Linux通常是3G。系统的虚拟内存也是分配的依据,首先是不能超过,然后由于操作系统支持硬盘来做部分的虚 拟内存,如果设置过大,那么对于应用响应来说势必有影响。再则就是要考虑同一台服务器上运行多个Java虚拟机所消耗的资源总合也不能超过可用资源。就和 前面OOM分析中的一样,其实由于OS的数据处理模型的限制,机器本身的硬件内存资源和虚拟内存资源并不一定会匹配,那么在有限的资源下如何调整好资源分 配,对于应用来说尤为重要。
    关于Heap的几个参数设置:
           说了Heap的有限资源问题以后,就来看看如何通过配置去改变JVM对于Heap的分配。下面所说的主要是对于Java Heap的分配,那么在申请了Java Heap以后,剩下的可用资源就会被使用到Native Heap。
           Xms: java heap初始化时的大小。默认情况是机器物理内存的1/64。这个主要是根据应用启动时消耗的资源决定,分配少了申请起来会降低启动速度,分配多了也浪费。
           Xmx:java heap的 最大值,默认是机器物理内存的1/4,最大也就到1G。这个值决定了最多可用的Java Heap Memory,分配过少就会在应用需要大量内存作缓存或者零时对象时出现OOM的问题,如果分配过大,那么就会产生上文提到的第二类OOM。所以如何配置 还是根据运行过程中的分析和计算来确定,如果不能确定还是采用默认的配置。
           Xmn:java heap新 生代的空间大小。在GC模型中,根据对象的生命周期的长短,产生了内存分代的设计:青年代(内部也分成三部分,类似于整体划分的作用,可以通过配置来设置 比例),老年代,持久代。每一代的管理和回收策略都不相同,最为活跃的就是青年代,同时这部分的内存分配和管理效率也是最高。通常情况下,对于内存的申请 优先在新生代中申请,当内存不够时会整理新生代,当整理以后还是不能满足申请的内存,就会向老年代移动一些生命周期较长的对象。这种整理和移动会消耗资 源,同时降低系统运行响应能力,因此如果青年代设置的过小,就会频繁的整理和移动,对性能造成影响。那是否把年青代设置的越大越好,其实不然,年青代采用 的是复制搜集算法,这种算法必须停止所有应用程序线程,服务器线程切换时间就会成为应用响应的瓶颈(当然永远不用收集那么就不存在这个问题)。老年代采用 的是串行标记收集的方式,并发收集可以减少对于应用的影响。
           Xss:线程堆栈最大值。允许更多的虚拟内存空间地址被Java Heap使用。
    以下是sun公司的性能优化白皮书中提到的几个例子:
    1.对于吞吐量的调优。机器配置:4G的内存,32个线程并发能力。
    java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
           -Xmx3800m -Xms3800m 配置了最大Java Heap来充分利用系统内存。
           -Xmn2g 创建足够大的青年代(可以并行被回收)充分利用系统内存,防止将短期对象复制到老年代。
        -Xss128 减少默认最大的线程栈大小,提供更多的处理虚拟内存地址空间被进程使用。
        -XX:+UseParallelGC 采用并行垃圾收集器对年青代的内存进行收集,提高效率。
        -XX:ParallelGCThreads=20 减少垃圾收集线程,默认是和服务器可支持的线程最大并发数相同,往往不需要配置到最大值。
    2.尝试采用对老年代并行收集
    java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
    -Xmx3550m -Xms3550m 内存分配被减小,因为ParallelOldGC会增加对于Native Heap的需求,因此需要减小Java Heap来满足需求。
    -XX:+UseParallelOldGC 采用对于老年代并发收集的策略,可以提高收集效率。
    3.提高吞吐量,减少应用停顿时间
    java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=31
    -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 选择了并发标记交换收集器,它可以并发执行收集操作,降低应用停止时间,同时它也是并行处理模式,可以有效地利用多处理器的系统的多进程处理。
    -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=31 表示在青年代中Eden和Survivor比例,设置增加了Survivor的大小,越大的survivor空间可以允许短期对象尽量在年青代消亡。
    -XX:TargetSurvivorRatio=90 允许90%的空间被占用,超过默认的50%,提高对于survivor的使用率。
    类似的例子网上很多,这儿就不在列下来了,最终是否采取自己配置来替换默认配置还是要根据虚拟机的使用情况来分析和配置。

     

    posted @ 2011-10-27 19:32 鸿雁 阅读(261) | 评论 (0)编辑 收藏

    Spring事务配置的五种方式

    Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource、TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分。

        DataSource、TransactionManager这两部分只是会根据数据访问方式有所变化,比如使用Hibernate进行数据访问时,DataSource实际为SessionFactory,TransactionManager的实现为HibernateTransactionManager。

        具体如下图:

    Spring事务配置 (2)

        根据代理机制的不同,总结了五种Spring事务的配置方式,配置文件如下:

        第一种方式:每个Bean都有一个代理

    <?xml version=”1.0″ encoding=”UTF-8″?>
    <beans xmlns=”http://www.springframework.org/schema/beans”
        xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
        xmlns:context=”http://www.springframework.org/schema/context”
        xmlns:aop=”http://www.springframework.org/schema/aop”
        xsi:schemaLocation=”http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
               http://www.springframework.org/schema/context
               http://www.springframework.org/schema/context/spring-context-2.5.xsd
               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd”>

     

        <bean id=”sessionFactory” 
                class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”> 
            <property name=”configLocation” value=”classpath:hibernate.cfg.xml” /> 
            <property name=”configurationClass” value=”org.hibernate.cfg.AnnotationConfiguration” />
        </bean> 

        <!– 定义事务管理器(声明式的事务) –> 
        <bean id=”transactionManager”
            class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
            <property name=”sessionFactory” ref=”sessionFactory” />
        </bean>
       
        <!– 配置DAO –>
        <bean id=”userDaoTarget” class=”com.bluesky.spring.dao.UserDaoImpl”>
            <property name=”sessionFactory” ref=”sessionFactory” />
        </bean>
       
        <bean id=”userDao” 
            class=”org.springframework.transaction.interceptor.TransactionProxyFactoryBean”> 
               <!– 配置事务管理器 –> 
               <property name=”transactionManager” ref=”transactionManager” />    
            <property name=”target” ref=”userDaoTarget” /> 
             <property name=”proxyInterfaces” value=”com.bluesky.spring.dao.GeneratorDao” />
            <!– 配置事务属性 –> 
            <property name=”transactionAttributes”> 
                <props> 
                    <prop key=”*”>PROPAGATION_REQUIRED</prop>
                </props> 
            </property> 
        </bean> 
    </beans>

        第二种方式:所有Bean共享一个代理基类

    <?xml version=”1.0″ encoding=”UTF-8″?>
    <beans xmlns=”http://www.springframework.org/schema/beans”
        xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
        xmlns:context=”http://www.springframework.org/schema/context”
        xmlns:aop=”http://www.springframework.org/schema/aop”
        xsi:schemaLocation=”http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
               http://www.springframework.org/schema/context
               http://www.springframework.org/schema/context/spring-context-2.5.xsd
               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd”>

     

        <bean id=”sessionFactory” 
                class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”> 
            <property name=”configLocation” value=”classpath:hibernate.cfg.xml” /> 
            <property name=”configurationClass” value=”org.hibernate.cfg.AnnotationConfiguration” />
        </bean> 

        <!– 定义事务管理器(声明式的事务) –> 
        <bean id=”transactionManager”
            class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
            <property name=”sessionFactory” ref=”sessionFactory” />
        </bean>
       
        <bean id=”transactionBase” 
                class=”org.springframework.transaction.interceptor.TransactionProxyFactoryBean” 
                lazy-init=”true” abstract=”true”> 
            <!– 配置事务管理器 –> 
            <property name=”transactionManager” ref=”transactionManager” /> 
            <!– 配置事务属性 –> 
            <property name=”transactionAttributes”> 
                <props> 
                    <prop key=”*”>PROPAGATION_REQUIRED</prop> 
                </props> 
            </property> 
        </bean>   
      
        <!– 配置DAO –>
        <bean id=”userDaoTarget” class=”com.bluesky.spring.dao.UserDaoImpl”>
            <property name=”sessionFactory” ref=”sessionFactory” />
        </bean>
       
        <bean id=”userDao” parent=”transactionBase” > 
            <property name=”target” ref=”userDaoTarget” />  
        </bean>
    </beans>

    第三种方式:使用拦截器

    <?xml version=”1.0″ encoding=”UTF-8″?>
    <beans xmlns=”http://www.springframework.org/schema/beans”
        xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
        xmlns:context=”http://www.springframework.org/schema/context”
        xmlns:aop=”http://www.springframework.org/schema/aop”
        xsi:schemaLocation=”http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
               http://www.springframework.org/schema/context
               http://www.springframework.org/schema/context/spring-context-2.5.xsd
               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd”>

     

        <bean id=”sessionFactory” 
                class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”> 
            <property name=”configLocation” value=”classpath:hibernate.cfg.xml” /> 
            <property name=”configurationClass” value=”org.hibernate.cfg.AnnotationConfiguration” />
        </bean> 

        <!– 定义事务管理器(声明式的事务) –> 
        <bean id=”transactionManager”
            class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
            <property name=”sessionFactory” ref=”sessionFactory” />
        </bean> 
      
        <bean id=”transactionInterceptor” 
            class=”org.springframework.transaction.interceptor.TransactionInterceptor”> 
            <property name=”transactionManager” ref=”transactionManager” /> 
            <!– 配置事务属性 –> 
            <property name=”transactionAttributes”> 
                <props> 
                    <prop key=”*”>PROPAGATION_REQUIRED</prop> 
                </props> 
            </property> 
        </bean>
         
        <bean class=”org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator”> 
            <property name=”beanNames”> 
                <list> 
                    <value>*Dao</value>
                </list> 
            </property> 
            <property name=”interceptorNames”> 
                <list> 
                    <value>transactionInterceptor</value> 
                </list> 
            </property> 
        </bean> 
     
        <!– 配置DAO –>
        <bean id=”userDao” class=”com.bluesky.spring.dao.UserDaoImpl”>
            <property name=”sessionFactory” ref=”sessionFactory” />
        </bean>
    </beans>

    第四种方式:使用tx标签配置的拦截器

    <?xml version=”1.0″ encoding=”UTF-8″?>
    <beans xmlns=”http://www.springframework.org/schema/beans”
        xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
        xmlns:context=”http://www.springframework.org/schema/context”
        xmlns:aop=”http://www.springframework.org/schema/aop”
        xmlns:tx=”http://www.springframework.org/schema/tx”
        xsi:schemaLocation=”http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
               http://www.springframework.org/schema/context
               http://www.springframework.org/schema/context/spring-context-2.5.xsd
               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
               http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd”>

     

        <context:annotation-config />
        <context:component-scan base-package=”com.bluesky” />

        <bean id=”sessionFactory” 
                class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”> 
            <property name=”configLocation” value=”classpath:hibernate.cfg.xml” /> 
            <property name=”configurationClass” value=”org.hibernate.cfg.AnnotationConfiguration” />
        </bean> 

        <!– 定义事务管理器(声明式的事务) –> 
        <bean id=”transactionManager”
            class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
            <property name=”sessionFactory” ref=”sessionFactory” />
        </bean>

        <tx:advice id=”txAdvice” transaction-manager=”transactionManager”>
            <tx:attributes>
                <tx:method name=”*” propagation=”REQUIRED” />
            </tx:attributes>
        </tx:advice>
       
        <aop:config>
            <aop:pointcut id=”interceptorPointCuts”
                expression=”execution(* com.bluesky.spring.dao.*.*(..))” />
            <aop:advisor advice-ref=”txAdvice”
                pointcut-ref=”interceptorPointCuts” />       
        </aop:config>     
    </beans>

    第五种方式:全注解

    <?xml version=”1.0″ encoding=”UTF-8″?>
    <beans xmlns=”http://www.springframework.org/schema/beans”
        xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
        xmlns:context=”http://www.springframework.org/schema/context”
        xmlns:aop=”http://www.springframework.org/schema/aop”
        xmlns:tx=”http://www.springframework.org/schema/tx”
        xsi:schemaLocation=”http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
               http://www.springframework.org/schema/context
               http://www.springframework.org/schema/context/spring-context-2.5.xsd
               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
               http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd”>

     

        <context:annotation-config />
        <context:component-scan base-package=”com.bluesky” />

        <tx:annotation-driven transaction-manager=”transactionManager”/>

        <bean id=”sessionFactory” 
                class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”> 
            <property name=”configLocation” value=”classpath:hibernate.cfg.xml” /> 
            <property name=”configurationClass” value=”org.hibernate.cfg.AnnotationConfiguration” />
        </bean> 

        <!– 定义事务管理器(声明式的事务) –> 
        <bean id=”transactionManager”
            class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
            <property name=”sessionFactory” ref=”sessionFactory” />
        </bean>
       
    </beans>

    此时在DAO上需加上@Transactional注解,如下:

    package com.bluesky.spring.dao;

     

    import java.util.List;

    import org.hibernate.SessionFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
    import org.springframework.stereotype.Component;

    import com.bluesky.spring.domain.User;

    @Transactional
    @Component(“userDao”)
    public class UserDaoImpl extends HibernateDaoSupport implements UserDao {

        public List<User> listUsers() {
            return this.getSession().createQuery(“from User”).list();
        }
       
       
    }

    posted @ 2011-10-27 19:29 鸿雁 阅读(289) | 评论 (0)编辑 收藏

    集群和分布式

    集群概念

    1. 两大关键特性
    集群是一组协同工作的服务实体,用以提供比单一服务实体更具扩展性与可用性的服务平台。在客户端看来,一个集群就象是一个服务实体,但事实上集群由一组服务实体组成。与单一服务实体相比较,集群提供了以下两个关键特性:

    ·  可扩展性--集群的性能不限于单一的服务实体,新的服务实体可以动态地加入到集群,从而增强集群的性能。

    ·  高可用性--集群通过服务实体冗余使客户端免于轻易遇到out of service的警告。在集群中,同样的服务可以由多个服务实体提供。如果一个服务实体失败了,另一个服务实体会接管失败的服务实体。集群提供的从一个出错的服务实体恢复到另一个服务实体的功能增强了应用的可用性。

    2. 两大能力
    为了具有可扩展性和高可用性特点,集群的必须具备以下两大能力:

    ·  负载均衡--负载均衡能把任务比较均衡地分布到集群环境下的计算和网络资源。

    ·  错误恢复--由于某种原因,执行某个任务的资源出现故障,另一服务实体中执行同一任务的资源接着完成任务。这种由于一个实体中的资源不能工作,另一个实体中的资源透明的继续完成任务的过程叫错误恢复。

    负载均衡和错误恢复都要求各服务实体中有执行同一任务的资源存在,而且对于同一任务的各个资源来说,执行任务所需的信息视图(信息上下文)必须是一样的。

    3. 两大技术
    实现集群务必要有以下两大技术:

    ·  集群地址--集群由多个服务实体组成,集群客户端通过访问集群的集群地址获取集群内部各服务实体的功能。具有单一集群地址(也叫单一影像)是集群的一个基本特征。维护集群地址的设置被称为负载均衡器。负载均衡器内部负责管理各个服务实体的加入和退出,外部负责集群地址向内部服务实体地址的转换。有的负载均衡器实现真正的负载均衡算法,有的只支持任务的转换。只实现任务转换的负载均衡器适用于支持ACTIVE-STANDBY的集群环境,在那里,集群中只有一个服务实体工作,当正在工作的服务实体发生故障时,负载均衡器把后来的任务转向另外一个服务实体。

    ·  内部通信--为了能协同工作、实现负载均衡和错误恢复,集群各实体间必须时常通信,比如负载均衡器对服务实体心跳测试信息、服务实体间任务执行上下文信息的通信。

    具有同一个集群地址使得客户端能访问集群提供的计算服务,一个集群地址下隐藏了各个服务实体的内部地址,使得客户要求的计算服务能在各个服务实体之间分布。内部通信是集群能正常运转的基础,它使得集群具有均衡负载和错误恢复的能力。

    集群分类

    Linux集群主要分成三大类( 高可用集群, 负载均衡集群,科学计算集群)

    高可用集群( High Availability Cluster)
    负载均衡集群(Load Balance Cluster)
    科学计算集群(High Performance Computing Cluster)
    ================================================

    具体包括:

    Linux High Availability 高可用集群                                      
    (普通两节点双机热备,多节点HA集群,RAC, shared, share-nothing集群等)

    Linux Load Balance 负载均衡集群                                     
     (LVS等....)

    Linux High Performance Computing 高性能科学计算集群    
     (Beowulf 类集群....)

    分布式存储                                                                        
    其他类linux集群             
    (如Openmosix, rendering farm 等..)

    详细介绍

    1. 高可用集群(High Availability Cluster)
    常见的就是2个节点做成的HA集群,有很多通俗的不科学的名称,比如"双机热备", "双机互备", "双机".
    高可用集群解决的是保障用户的应用程序持续对外提供服务的能力。 (请注意高可用集群既不是用来保护业务数据的,保护的是用户的业务程序对外不间断提供服务,把因软件/硬件/人为造成的故障对业务的影响降低到最小程度)。

    2. 负载均衡集群(Load Balance Cluster)

    负载均衡系统:集群中所有的节点都处于活动状态,它们分摊系统的工作负载。一般Web服务器集群、数据库集群和应用服务器集群都属于这种类型。

    负载均衡集群一般用于相应网络请求的网页服务器,数据库服务器。这种集群可以在接到请求时,检查接受请求较少,不繁忙的服务器,并把请求转到这些服务器上。从检查其他服务器状态这一点上看,负载均衡和容错集群很接近,不同之处是数量上更多。

    3. 科学计算集群(High Performance Computing Cluster)

    高性能计算(High Perfermance Computing)集群,简称HPC集群。这类集群致力于提供单个计算机所不能提供的强大的计算能力。

    高性能计算分类  
     高吞吐计算(High-throughput Computing)
      有一类高性能计算,可以把它分成若干可以并行的子任务,而且各个子任务彼此间没有什么关联。象在家搜寻外星人( SETI@HOME -- Search for Extraterrestrial Intelligence at Home )就是这一类型应用。这一项目是利用Internet上的闲置的计算资源来搜寻外星人。SETI项目的服务器将一组数据和数据模式发给Internet上参加SETI的计算节点,计算节点在给定的数据上用给定的模式进行搜索,然后将搜索的结果发给服务器。服务器负责将从各个计算节点返回的数据汇集成完整的数据。因为这种类型应用的一个共同特征是在海量数据上搜索某些模式,所以把这类计算称为高吞吐计算。所谓的Internet计算都属于这一类。按照Flynn的分类,高吞吐计算属于SIMD(Single Instruction/Multiple Data)的范畴。

     分布计算(Distributed Computing)
      另一类计算刚好和高吞吐计算相反,它们虽然可以给分成若干并行的子任务,但是子任务间联系很紧密,需要大量的数据交换。按照Flynn的分类,分布式的高性能计算属于MIMD(Multiple Instruction/Multiple Data)的范畴。

    4. 分布式(集群)与集群的联系与区别
    分布式是指将不同的业务分布在不同的地方。
    而集群指的是将几台服务器集中在一起,实现同一业务。
    分布式中的每一个节点,都可以做集群。
    而集群并不一定就是分布式的。
    举例:就比如新浪网,访问的人多了,他可以做一个群集,前面放一个响应服务器,后面几台服务器完成同一业务,如果有业务访问的时候,响应服务器看哪台服务器的负载不是很重,就将给哪一台去完成。
    而分布式,从窄意上理解,也跟集群差不多, 但是它的组织比较松散,不像集群,有一个组织性,一台服务器垮了,其它的服务器可以顶上来。
    分布式的每一个节点,都完成不同的业务,一个节点垮了,哪这个业务就不可访问了。

    posted @ 2011-10-27 19:28 鸿雁 阅读(377) | 评论 (0)编辑 收藏

    软件设计的七大原则

    设计模式遵循的一般原则:

    1.开-闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开发,对修改关闭.说的是,再设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展.换言之,应当可以在不必修改源代码的情况下改变这个模块的行为,在保持系统一定稳定性的基础上,对系统进行扩展。这是面向对象设计(OOD)的基石,也是最重要的原则。

    2.里氏代换原则(Liskov Substitution Principle,常缩写为.LSP)
    (1).由Barbar Liskov(芭芭拉.里氏)提出,是继承复用的基石。
    (2).严格表达:如果每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换称o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型.
        换言之,一个软件实体如果使用的是一个基类的话,那么一定适用于其子类,而且它根本不能察觉出基类对象和子类对象的区别.只有衍生类可以替换基类,软件单位的功能才能不受影响,基类才能真正被复用,而衍生类也能够在基类的基础上增加新功能。
    (3).反过来的代换不成立
    (4).<墨子.小取>中说:"白马,马也; 乘白马,乘马也.骊马(黑马),马也;乘骊马,乘马也."
    (5).该类西方著名的例程为:正方形是否是长方形的子类(答案是"否")。类似的还有椭圆和圆的关系。
    (6).应当尽量从抽象类继承,而不从具体类继承,一般而言,如果有两个具体类A,B有继承关系,那么一个最简单的修改方案是建立一个抽象类C,然后让类A和B成为抽象类C的子类.即如果有一个由继承关系形成的登记结构的话,那么在等级结构的树形图上面所有的树叶节点都应当是具体类;而所有的树枝节点都应当是抽象类或者接口.
    (7)."基于契约设计(Design By Constract),简称DBC"这项技术对LISKOV代换原则提供了支持.该项技术Bertrand Meyer伯特兰做过详细的介绍:
    使用DBC,类的编写者显式地规定针对该类的契约.客户代码的编写者可以通过该契约获悉可以依赖的行为方式.契约是通过每个方法声明的前置条件(preconditions)和后置条件(postconditions)来指定的.要使一个方法得以执行,前置条件必须为真.执行完毕后,该方法要保证后置条件为真.就是说,在重新声明派生类中的例程(routine)时,只能使用相等或者更弱的前置条件来替换原始的前置条件,只能使用相等或者更强的后置条件来替换原始的后置条件.

    3.依赖倒置原则(Dependence Inversion Principle),要求客户端依赖于抽象耦合.
    (1)表述:抽象不应当依赖于细节,细节应当依赖于抽象.(Program to an interface, not an implementaction)
    (2)表述二:针对接口编程的意思是说,应当使用接口和抽象类进行变量的类型声明,参量的类型声明,方法的返还类型声明,以及数据类型的转换等.不要针对实现编程的意思就是说,不应当使用具体类进行变量的类型声明,参量类型声明,方法的返还类型声明,以及数据类型的转换等.
       要保证做到这一点,一个具体的类应等只实现接口和抽象类中声明过的方法,而不应当给出多余的方法.
       只要一个被引用的对象存在抽象类型,就应当在任何引用此对象的地方使用抽象类型,包括参量的类型声明,方法返还类型的声明,属性变量的类型声明等.
    (3)接口与抽象的区别就在于抽象类可以提供某些方法的部分实现,而接口则不可以,这也大概是抽象类唯一的优点.如果向一个抽象类加入一个新的具体方法,那么所有的子类型一下子就都得到得到了这个新的具体方法,而接口做不到这一点.如果向一个接口加入了一个新的方法的话,所有实现这个接口的类就全部不能通过编译了,因为它们都没有实现这个新声明的方法.这显然是接口的一个缺点.
    (4)一个抽象类的实现只能由这个抽象类的子类给出,也就是说,这个实现处在抽象类所定义出的继承的登记结构中,而由于一般语言都限制一个类只能从最多一个超类继承,因此将抽象作为类型定义工具的效能大打折扣.
       反过来,看接口,就会发现任何一个实现了一个接口所规定的方法的类都可以具有这个接口的类型,而一个类可以实现任意多个接口.
    (5)从代码重构的角度上讲,将一个单独的具体类重构成一个接口的实现是很容易的,只需要声明一个接口,并将重要的方法添加到接口声明中,然后在具体类定义语句中加上保留字以继承于该接口就行了.
       而作为一个已有的具体类添加一个抽象类作为抽象类型不那么容易,因为这个具体类有可能已经有一个超类.这样一来,这个新定义的抽象类只好继续向上移动,变成这个超类的超类,如此循环,最后这个新的抽象类必定处于整个类型等级结构的最上端,从而使登记结构中的所有成员都会受到影响.
    (6)接口是定义混合类型的理想工具,所为混合类型,就是在一个类的主类型之外的次要类型.一个混合类型表明一个类不仅仅具有某个主类型的行为,而且具有其他的次要行为.
    (7)联合使用接口和抽象类:
       由于抽象类具有提供缺省实现的优点,而接口具有其他所有优点,所以联合使用两者就是一个很好的选择.
       首先,声明类型的工作仍然接口承担的,但是同时给出的还有一个抽象类,为这个接口给出一个缺省实现.其他同属于这个抽象类型的具体类可以选择实现这个接口,也可以选择继承自这个抽象类.如果一个具体类直接实现这个接口的话,它就必须自行实现所有的接口;相反,如果它继承自抽象类的话,它可以省去一些不必要的的方法,因为它可以从抽象类中自动得到这些方法的缺省实现;如果需要向接口加入一个新的方法的话,那么只要同时向这个抽象类加入这个方法的一个具体实现就可以了,因为所有继承自这个抽象类的子类都会从这个抽象类得到这个具体方法.这其实就是缺省适配器模式(Defaule Adapter).
    (8)什么是高层策略呢?它是应用背后的抽象,是那些不随具体细节的改变而改变的真理. 它是系统内部的系统____隐喻.

    4.接口隔离原则(Interface Segregation Principle, ISP)
    (1)一个类对另外一个类的依赖是建立在最小的接口上。

    (2)使用多个专门的接口比使用单一的总接口要好.根据客户需要的不同,而为不同的客户端提供不同的服务是一种应当得到鼓励的做法.就像"看人下菜碟"一样,要看客人是谁,再提供不同档次的饭菜.
    (3)胖接口会导致他们的客户程序之间产生不正常的并且有害的耦合关系.当一个客户程序要求该胖接口进行一个改动时,会影响到所有其他的客户程序.因此客户程序应该仅仅依赖他们实际需要调用的方法.
        
    5.合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
    在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过这些向对象的委派达到复用已有功能的目的.这个设计原则有另一个简短的表述:要尽量使用合成/聚合,尽量不要使用继承.

    6.迪米特法则(Law of Demeter LoD)又叫做最少知识原则(Least Knowledge Principle,LKP),就是说,一个对象应当对其他对象有尽可能少的了了解.
    迪米特法则最初是用来作为面向对象的系统设计风格的一种法则,与1987年秋天由Ian Holland在美国东北大学为一个叫做迪米特(Demeter)的项目设计提出的,因此叫做迪米特法则[LIEB89][LIEB86].这条法则实际上是很多著名系统,比如火星登陆软件系统,木星的欧罗巴卫星轨道飞船的软件系统的指导设计原则.
    没有任何一个其他的OO设计原则象迪米特法则这样有如此之多的表述方式,如下几种:
    (1)只与你直接的朋友们通信(Only talk to your immediate friends)
    (2)不要跟"陌生人"说话(Don't talk to strangers)
    (3)每一个软件单位对其他的单位都只有最少的知识,而且局限于那些本单位密切相关的软件单位.
    就是说,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。

    7.单一职责原则(Simple responsibility pinciple SRP)
    就一个类而言,应该仅有一个引起它变化的原因,如果你能想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责.应该把多于的指责分离出去,分别再创建一些类来完成每一个职责.

    另外:常说的OO五大原则就是指其中的 :1、单一职责原则;2、开放闭合原则;3、里氏替换原则;4、依赖倒置原则;5、接口隔离原则。

    posted @ 2011-10-27 19:27 鸿雁 阅读(299) | 评论 (0)编辑 收藏

    各种数据库分页sql

    Mysql分页采用limt关键字

    select * from t_order limit 5,10; #返回第6-15行数据
    select
     * from  t_order limit  5; #返回前5
    select
     * from  t_order limit  0,5; #返回前5

    Mssql 2000分页采用top关键字(20005以上版本也支持关键字rownum)

    Select top 10 * from t_order where id not in (select id from t_order where id>5 ); //返回第615行数据

    其中10表示取10记录 5表示从第5条记录开始取

    Oracle分页

    ①采用rownum关键字(三层嵌套)

    SELECT * FROM(
     SELECT A.*,ROWNUM num FROM
     (SELECT * FROM t_order)A
     WHERE
     ROWNUM<=15)
    WHERE num>=5;--返回第5-15行数据

    ②采用row_number解析函数进行分页(效率更高)

    SELECT xx.* FROM(
    SELECT t.*,row_number() over(ORDER BY o_id)AS num
    FROM t_order t
    )xx
    WHERE num BETWEEN 5 AND 15;

    --返回第5-15行数据

    解析函数能用格式

    函数() over(pertion by 字段 order by 字段);

    Pertion 按照某个字段分区

    Order 按照勒个字段排序

    posted @ 2011-10-27 12:13 鸿雁 阅读(329) | 评论 (0)编辑 收藏

    Struts1和Struts2的区别

    Struts1和Struts2的区别和对比:

    Action 类:
    • Struts1要求Action类继承一个抽象基类。Struts1的一个普遍问题是使用抽象类编程而不是接口。
    • Struts 2 Action类可以实现一个Action接口,也可实现其他接口,使可选和定制的服务成为可能。Struts2提供一个ActionSupport基类去 实现 常用的接口。Action接口不是必须的,任何有execute标识的POJO对象都可以用作Struts2的Action对象。

    线程模式:
    • Struts1 Action是单例模式并且必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts1 Action能作的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的。
    • Struts2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。(实际上,servlet容器给每个请求产生许多可丢弃的对象,并且不会导致性能和垃圾回收问题)

    Servlet 依赖:
    • Struts1 Action 依赖于Servlet API ,因为当一个Action被调用时HttpServletRequest 和 HttpServletResponse 被传递给execute方法。
    • Struts 2 Action不依赖于容器,允许Action脱离容器单独被测试。如果需要,Struts2 Action仍然可以访问初始的request和response。但是,其他的元素减少或者消除了直接访问HttpServetRequest 和 HttpServletResponse的必要性。

    可测性:
    • 测试Struts1 Action的一个主要问题是execute方法暴露了servlet API(这使得测试要依赖于容器)。一个第三方扩展--Struts TestCase--提供了一套Struts1的模拟对象(来进行测试)。
    • Struts 2 Action可以通过初始化、设置属性、调用方法来测试,“依赖注入”支持也使测试更容易。

    捕获输入:
    • Struts1 使用ActionForm对象捕获输入。所有的ActionForm必须继承一个基类。因为其他JavaBean不能用作ActionForm,开发者经 常创建多余的类捕获输入。动态Bean(DynaBeans)可以作为创建传统ActionForm的选择,但是,开发者可能是在重新描述(创建)已经存 在的JavaBean(仍然会导致有冗余的javabean)。
    • Struts 2直接使用Action属性作为输入属性,消除了对第二个输入对象的需求。输入属性可能是有自己(子)属性的rich对象类型。Action属性能够通过 web页面上的taglibs访问。Struts2也支持ActionForm模式。rich对象类型,包括业务对象,能够用作输入/输出对象。这种 ModelDriven 特性简化了taglib对POJO输入对象的引用。

    表达式语言:
    • Struts1 整合了JSTL,因此使用JSTL EL。这种EL有基本对象图遍历,但是对集合和索引属性的支持很弱。
    • Struts2可以使用JSTL,但是也支持一个更强大和灵活的表达式语言--"Object Graph Notation Language" (OGNL).

    绑定值到页面(view):
    • Struts 1使用标准JSP机制把对象绑定到页面中来访问。
    • Struts 2 使用 "ValueStack"技术,使taglib能够访问值而不需要把你的页面(view)和对象绑定起来。ValueStack策略允许通过一系列名称相同但类型不同的属性重用页面(view)。

    类型转换:
    • Struts 1 ActionForm 属性通常都是String类型。Struts1使用Commons-Beanutils进行类型转换。每个类一个转换器,对每一个实例来说是不可配置的。
    • Struts2 使用OGNL进行类型转换。提供基本和常用对象的转换器。

    校验:
    • Struts 1支持在ActionForm的validate方法中手动校验,或者通过Commons Validator的扩展来校验。同一个类可以有不同的校验内容,但不能校验子对象。
    • Struts2支持通过validate方法和XWork校验框架来进行校验。XWork校验框架使用为属性类类型定义的校验和内容校验,来支持chain校验子属性

    Action执行的控制:
    • Struts1支持每一个模块有单独的Request Processors(生命周期),但是模块中的所有Action必须共享相同的生命周期。
    • Struts2支持通过拦截器堆栈(Interceptor Stacks)为每一个Action创建不同的生命周期。堆栈能够根据需要和不同的Action一起使用。

    posted @ 2011-10-26 23:55 鸿雁 阅读(127) | 评论 (0)编辑 收藏

    Spring源代码解析(五):Spring声明式事务处理

         摘要: 我们看看Spring中的事务处理的代码,使用Spring管理事务有声明式和编程式两种方式,声明式事务处理通过AOP的实现把事物管理代码作为方面封装来横向插入到业务代码中,使得事务管理代码和业务代码解藕。在这种方式我们结合IoC容器和Spirng已有的FactoryBean来对事务管理进行属性配置,比如传播行为,隔离级别等。其中最简单的方式就是通过配置TransactionProxyFactoryB...  阅读全文

    posted @ 2011-10-26 23:40 鸿雁 阅读(580) | 评论 (0)编辑 收藏

    Spring源代码解析(六):Spring AOP中对拦截器调用的实现

    前面我们分析了Spring AOP实现中得到Proxy对象的过程,下面我们看看在Spring AOP中拦截器链是怎样被调用的,也就是Proxy模式是怎样起作用的,或者说Spring是怎样为我们提供AOP功能的;
    在JdkDynamicAopProxy中生成Proxy对象的时候:
    Java代码 复制代码 收藏代码
    1. return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);  

    这里的this参数对应的是InvocationHandler对象,这里我们的JdkDynamicAopProxy实现了这个接口,也就是说当Proxy对象的函数被调用的时候,这个InvocationHandler的invoke方法会被作为回调函数调用,下面我们看看这个方法的实现:
    Java代码 复制代码 收藏代码
    1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {   
    2.     MethodInvocation invocation = null;   
    3.     Object oldProxy = null;   
    4.     boolean setProxyContext = false;   
    5.   
    6.     TargetSource targetSource = this.advised.targetSource;   
    7.     Class targetClass = null;   
    8.     Object target = null;   
    9.   
    10.     try {   
    11.         // Try special rules for equals() method and implementation of the   
    12.         // Advised AOP configuration interface.   
    13.   
    14.         if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {   
    15.             // What if equals throws exception!?   
    16.             // This class implements the equals(Object) method itself.   
    17.             return equals(args[0]) ? Boolean.TRUE : Boolean.FALSE;   
    18.         }   
    19.         if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {   
    20.             // This class implements the hashCode() method itself.   
    21.             return new Integer(hashCode());   
    22.         }   
    23.         if (Advised.class == method.getDeclaringClass()) {   
    24.             // service invocations on ProxyConfig with the proxy config   
    25.             return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);   
    26.         }   
    27.   
    28.         Object retVal = null;   
    29.   
    30.         if (this.advised.exposeProxy) {   
    31.             // make invocation available if necessary   
    32.             oldProxy = AopContext.setCurrentProxy(proxy);   
    33.             setProxyContext = true;   
    34.         }   
    35.   
    36.         // May be <code>null</code>. Get as late as possible to minimize the time we "own" the target,   
    37.         // in case it comes from a pool.   
    38.         // 这里是得到目标对象的地方,当然这个目标对象可能来自于一个实例池或者是一个简单的JAVA对象   
    39.         target = targetSource.getTarget();   
    40.         if (target != null) {   
    41.             targetClass = target.getClass();   
    42.         }   
    43.   
    44.         // get the interception chain for this method   
    45.         // 这里获得定义好的拦截器链   
    46.         List chain = this.advised.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(   
    47.                 this.advised, proxy, method, targetClass);   
    48.   
    49.         // Check whether we have any advice. If we don't, we can fallback on direct   
    50.         // reflective invocation of the target, and avoid creating a MethodInvocation.   
    51.         // 如果没有设定拦截器,那么我们就直接调用目标的对应方法   
    52.         if (chain.isEmpty()) {   
    53.             // We can skip creating a MethodInvocation: just invoke the target directly   
    54.             // Note that the final invoker must be an InvokerInterceptor so we know it does   
    55.             // nothing but a reflective operation on the target, and no hot swapping or fancy proxying   
    56.             retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);   
    57.         }   
    58.         else {   
    59.             // We need to create a method invocation...   
    60.             // invocation = advised.getMethodInvocationFactory().getMethodInvocation(   
    61.             //         proxy, method, targetClass, target, args, chain, advised);   
    62.             // 如果有拦截器的设定,那么需要调用拦截器之后才调用目标对象的相应方法   
    63.             // 这里通过构造一个ReflectiveMethodInvocation来实现,下面我们会看这个ReflectiveMethodInvocation类   
    64.             invocation = new ReflectiveMethodInvocation(   
    65.                     proxy, target, method, args, targetClass, chain);   
    66.   
    67.             // proceed to the joinpoint through the interceptor chain   
    68.             // 这里通过ReflectiveMethodInvocation来调用拦截器链和相应的目标方法   
    69.             retVal = invocation.proceed();   
    70.         }   
    71.   
    72.         // massage return value if necessary   
    73.         if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy)) {   
    74.             // Special case: it returned "this" and the return type of the method is type-compatible   
    75.             // Note that we can't help if the target sets   
    76.             // a reference to itself in another returned object.   
    77.             retVal = proxy;   
    78.         }   
    79.         return retVal;   
    80.     }   
    81.     finally {   
    82.         if (target != null && !targetSource.isStatic()) {   
    83.             // must have come from TargetSource   
    84.             targetSource.releaseTarget(target);   
    85.         }   
    86.   
    87.         if (setProxyContext) {   
    88.             // restore old proxy   
    89.             AopContext.setCurrentProxy(oldProxy);   
    90.         }   
    91.     }   
    92. }  

    我们先看看目标对象方法的调用,这里是通过AopUtils的方法调用 - 使用反射机制来对目标对象的方法进行调用:
    Java代码 复制代码 收藏代码
    1. public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args)   
    2.     throws Throwable {   
    3.   
    4.     // Use reflection to invoke the method.   
    5.     // 利用放射机制得到相应的方法,并且调用invoke   
    6.     try {   
    7.         if (!Modifier.isPublic(method.getModifiers()) ||   
    8.                 !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {   
    9.             method.setAccessible(true);   
    10.         }   
    11.         return method.invoke(target, args);   
    12.     }   
    13.     catch (InvocationTargetException ex) {   
    14.         // Invoked method threw a checked exception.   
    15.         // We must rethrow it. The client won't see the interceptor.   
    16.         throw ex.getTargetException();   
    17.     }   
    18.     catch (IllegalArgumentException ex) {   
    19.         throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +   
    20.                 method + "] on target [" + target + "]", ex);   
    21.     }   
    22.     catch (IllegalAccessException ex) {   
    23.         throw new AopInvocationException("Couldn't access method: " + method, ex);   
    24.     }   
    25. }  

    对拦截器链的调用处理是在ReflectiveMethodInvocation里实现的:
    Java代码 复制代码 收藏代码
    1. public Object proceed() throws Throwable {   
    2.     //    We start with an index of -1 and increment early.   
    3.     // 这里直接调用目标对象的方法,没有拦截器的调用或者拦截器已经调用完了,这个currentInterceptorIndex的初始值是0   
    4.     if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()) {   
    5.         return invokeJoinpoint();   
    6.     }   
    7.   
    8.     Object interceptorOrInterceptionAdvice =   
    9.         this.interceptorsAndDynamicMethodMatchers.get(this.currentInterceptorIndex);   
    10.     if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {   
    11.         // Evaluate dynamic method matcher here: static part will already have   
    12.         // been evaluated and found to match.   
    13.         // 这里获得相应的拦截器,如果拦截器可以匹配的上的话,那就调用拦截器的invoke方法   
    14.         InterceptorAndDynamicMethodMatcher dm =   
    15.             (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;   
    16.         if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {   
    17.             return dm.interceptor.invoke(nextInvocation());   
    18.         }   
    19.         else {   
    20.             // Dynamic matching failed.   
    21.             // Skip this interceptor and invoke the next in the chain.   
    22.             // 如果拦截器匹配不上,那就调用下一个拦截器,这个时候拦截器链的位置指示后移并迭代调用当前的proceed方法   
    23.             this.currentInterceptorIndex++;   
    24.             return proceed();   
    25.         }   
    26.     }   
    27.     else {   
    28.         // It's an interceptor, so we just invoke it: The pointcut will have   
    29.         // been evaluated statically before this object was constructed.   
    30.         return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(nextInvocation());   
    31.     }   
    32. }  

    这里把当前的拦截器链以及在拦截器链的位置标志都clone到一个MethodInvocation对象了,作用是当前的拦截器执行完之后,会继续沿着得到这个拦截器链执行下面的拦截行为,也就是会迭代的调用上面这个proceed:
    Java代码 复制代码 收藏代码
    1. private ReflectiveMethodInvocation nextInvocation() throws CloneNotSupportedException {   
    2.     ReflectiveMethodInvocation invocation = (ReflectiveMethodInvocation) clone();   
    3.     invocation.currentInterceptorIndex = this.currentInterceptorIndex + 1;   
    4.     invocation.parent = this;   
    5.     return invocation;   
    6. }  

    这里的nextInvocation就已经包含了当前的拦截链的基本信息,我们看到在Interceptor中的实现比如TransactionInterceptor的实现中:
    Java代码 复制代码 收藏代码
    1. public Object invoke(final MethodInvocation invocation) throws Throwable {   
    2.    ......//这里是TransactionInterceptor插入的事务处理代码,我们会在后面分析事务处理实现的时候进行分析   
    3.         try {   
    4.             //这里是对配置的拦截器链进行迭代处理的调用   
    5.             retVal = invocation.proceed();   
    6.         }   
    7.    ......//省略了和事务处理的异常处理代码 ,也是TransactionInterceptor插入的处理   
    8.       else {   
    9.         try {   
    10.             Object result = ((CallbackPreferringPlatformTransactionManager) getTransactionManager()).execute(txAttr,   
    11.                     new TransactionCallback() {   
    12.                         public Object doInTransaction(TransactionStatus status) {   
    13.                              //这里是TransactionInterceptor插入对事务处理的代码   
    14.                             TransactionInfo txInfo = prepareTransactionInfo(txAttr, joinpointIdentification, status);   
    15.                             //这里是对配置的拦截器链进行迭代处理的调用,接着顺着拦截器进行处理   
    16.                             try {                           
    17.                                 return invocation.proceed();   
    18.                             }   
    19.    ......//省略了和事务处理的异常处理代码 ,也是TransactionInterceptor插入的处理   
    20.    }  

    从上面的分析我们看到了Spring AOP的基本实现,比如Spring怎样得到Proxy,怎样利用JAVA Proxy以及反射机制对用户定义的拦截器链进行处理。

    posted @ 2011-10-26 23:40 鸿雁 阅读(251) | 评论 (0)编辑 收藏

    Spring源代码解析(四):Spring AOP获取Proxy

    下面我们来看看Spring的AOP的一些相关代码是怎么得到Proxy的,让我们我们先看看AOP和Spring AOP的一些基本概念:
    Advice:
        通知,制定在连接点做什么,在Sping中,他主要描述Spring围绕方法调用注入的额外的行为,Spring提供的通知类型有:
        before advice,AfterReturningAdvice,ThrowAdvice,MethodBeforeAdvice,这些都是Spring AOP定义的接口类,具体的动作实现需要用户程序来完成。
    Pointcut:
        切点,其决定一个advice应该应用于哪个连接点,也就是需要插入额外处理的地方的集合,例如,被某个advice作为目标的一组方法。Spring pointcut通常意味着标示方法,可以选择一组方法调用作为pointcut,Spring提供了具体的切点来给用户使用,比如正则表达式切点 JdkRegexpMethodPointcut通过正则表达式对方法名进行匹配,其通过使用 AbstractJdkRegexpMethodPointcut中的对MethodMatcher接口的实现来完成pointcut功能:
    Java代码 复制代码 收藏代码
    1. public final boolean matches(Method method, Class targetClass) {   
    2.     //这里通过放射得到方法的全名   
    3.     String patt = method.getDeclaringClass().getName() + "." + method.getName();   
    4.     for (int i = 0; i < this.patterns.length; i++) {   
    5.         // 这里是判断是否和方法名是否匹配的代码   
    6.         boolean matched = matches(patt, i);   
    7.         if (matched) {   
    8.             for (int j = 0; j < this.excludedPatterns.length; j++) {   
    9.                 boolean excluded = matchesExclusion(patt, j);   
    10.                 if(excluded) {   
    11.                     return false;   
    12.                 }   
    13.             }   
    14.             return true;   
    15.         }   
    16.     }   
    17.     return false;   
    18. }  

    在JDKRegexpMethodPointcut中通过JDK中的正则表达式匹配来完成pointcut的最终确定:
    Java代码 复制代码 收藏代码
    1. protected boolean matches(String pattern, int patternIndex) {   
    2.     Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern);   
    3.     return matcher.matches();   
    4. }  

    Advisor:
    当我们完成额外的动作设计(advice)和额外动作插入点的设计(pointcut)以后,我们需要一个对象把他们结合起来,这就是通知器 - advisor,定义应该在哪里应用哪个通知。Advisor的实现有:DefaultPointcutAdvisor他有两个属性advice和 pointcut来让我们配置advice和pointcut。
    接着我们就可以通过ProxyFactoryBean来配置我们的代理对象和方面行为,在ProxyFactoryBean中有interceptorNames来配置已经定义好的通知器-advisor,虽然这里的名字叫做interceptNames,但实际上是供我们配置advisor的地方,具体的代理实现通过JDK 的Proxy或者CGLIB来完成。因为ProxyFactoryBean是一个FactoryBean,在ProxyFactoryBean中我们通过getObject()可以直接得到代理对象:
    Java代码 复制代码 收藏代码
    1. public Object getObject() throws BeansException {   
    2.     //这里初始化通知器链   
    3.     initializeAdvisorChain();   
    4.     if (isSingleton()) {   
    5.     //根据定义需要生成单件的Proxy   
    6.         return getSingletonInstance();   
    7.     }   
    8.     else {   
    9.     .......   
    10.         //这里根据定义需要生成Prototype类型的Proxy   
    11.         return newPrototypeInstance();   
    12.     }   
    13. }  

    我们看看怎样生成单件的代理对象:
    Java代码 复制代码 收藏代码
    1. private synchronized Object getSingletonInstance() {   
    2.     if (this.singletonInstance == null) {   
    3.         this.targetSource = freshTargetSource();   
    4.         if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {   
    5.             // 这里设置代理对象的接口   
    6.             setInterfaces(ClassUtils.getAllInterfacesForClass(this.targetSource.getTargetClass()));   
    7.         }   
    8.         // Eagerly initialize the shared singleton instance.   
    9.         super.setFrozen(this.freezeProxy);   
    10.         // 注意这里的方法会使用ProxyFactory来生成我们需要的Proxy   
    11.         this.singletonInstance = getProxy(createAopProxy());   
    12.         // We must listen to superclass advice change events to recache the singleton   
    13.         // instance if necessary.   
    14.         addListener(this);   
    15.     }   
    16.     return this.singletonInstance;   
    17. }   
    18.   
    19. //使用createAopProxy放回的AopProxy来得到代理对象。   
    20. protected Object getProxy(AopProxy aopProxy) {   
    21.     return aopProxy.getProxy(this.beanClassLoader);   
    22. }  

    ProxyFactoryBean的父类是AdvisedSupport,Spring使用AopProxy接口把AOP代理的实现与框架的其他部分分离开来;在AdvisedSupport中通过这样的方式来得到AopProxy,当然这里需要得到AopProxyFactory的帮助 - 下面我们看到Spring为我们提供的实现,来帮助我们方便的从JDK或者cglib中得到我们想要的代理对象:
    Java代码 复制代码 收藏代码
    1. protected synchronized AopProxy createAopProxy() {   
    2.     if (!this.isActive) {   
    3.         activate();   
    4.     }   
    5.     return getAopProxyFactory().createAopProxy(this);   
    6. }  

    而在ProxyConfig中对使用的AopProxyFactory做了定义:
    Java代码 复制代码 收藏代码
    1. //这个DefaultAopProxyFactory是Spring用来生成AopProxy的地方,   
    2. //当然了它包含JDK和Cglib两种实现方式。   
    3. private transient AopProxyFactory aopProxyFactory = new DefaultAopProxyFactory();  

    其中在DefaultAopProxyFactory中是这样生成AopProxy的:
    Java代码 复制代码 收藏代码
    1. public AopProxy createAopProxy(AdvisedSupport advisedSupport) throws AopConfigException {   
    2.     //首先考虑使用cglib来实现代理对象,当然如果同时目标对象不是接口的实现类的话   
    3.     if (advisedSupport.isOptimize() || advisedSupport.isProxyTargetClass() ||   
    4.         advisedSupport.getProxiedInterfaces().length == 0) {   
    5.         //这里判断如果不存在cglib库,直接抛出异常。   
    6.         if (!cglibAvailable) {   
    7.             throw new AopConfigException(   
    8.                     "Cannot proxy target class because CGLIB2 is not available. " +   
    9.                     "Add CGLIB to the class path or specify proxy interfaces.");   
    10.         }   
    11.         // 这里使用Cglib来生成Proxy,如果target不是接口的实现的话,返回cglib类型的AopProxy   
    12.         return CglibProxyFactory.createCglibProxy(advisedSupport);   
    13.     }   
    14.     else {   
    15.         // 这里使用JDK来生成Proxy,返回JDK类型的AopProxy   
    16.         return new JdkDynamicAopProxy(advisedSupport);   
    17.     }   
    18. }  

    于是我们就可以看到其中的代理对象可以由JDK或者Cglib来生成,我们看到JdkDynamicAopProxy类和Cglib2AopProxy都实现的是AopProxy的接口,在JdkDynamicAopProxy实现中我们可以看到Proxy是怎样生成的:
    Java代码 复制代码 收藏代码
    1. public Object getProxy(ClassLoader classLoader) {   
    2.     if (logger.isDebugEnabled()) {   
    3.         Class targetClass = this.advised.getTargetSource().getTargetClass();   
    4.         logger.debug("Creating JDK dynamic proxy" +   
    5.                 (targetClass != null ? " for [" + targetClass.getName() + "]" : ""));   
    6.     }   
    7.     Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);   
    8.     findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);   
    9.     //这里我们调用JDK Proxy来生成需要的Proxy实例   
    10.     return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);   
    11. }  

    这样用Proxy包装target之后,通过ProxyFactoryBean得到对其方法的调用就被Proxy拦截了, ProxyFactoryBean的getObject()方法得到的实际上是一个Proxy了,我们的target对象已经被封装了。对 ProxyFactoryBean这个工厂bean而言,其生产出来的对象是封装了目标对象的代理对象。

    posted @ 2011-10-26 23:39 鸿雁 阅读(234) | 评论 (0)编辑 收藏

    Spring源代码解析(三):Spring JDBC

    下面我们看看Spring JDBC相关的实现,
    在Spring中,JdbcTemplate是经常被使用的类来帮助用户程序操作数据库,在JdbcTemplate为用户程序提供了许多便利的数据库操作方法,比如查询,更新等,而且在Spring中,有许多类似 JdbcTemplate的模板,比如HibernateTemplate等等 - 看来这是Rod.Johnson的惯用手法,一般而言这种Template中都是通过回调函数CallBack类的使用来完成功能的,客户需要在回调接口中实现自己需要的定制行为,比如使用客户想要用的SQL语句等。不过往往Spring通过这种回调函数的实现已经为我们提供了许多现成的方法供客户使用。一般来说回调函数的用法采用匿名类的方式来实现,比如:
    Java代码 复制代码 收藏代码
    1. JdbcTemplate = new JdbcTemplate(datasource);   
    2. jdbcTemplate.execute(new CallBack(){   
    3.             public CallbackInterfacedoInAction(){   
    4.                ......   
    5.                //用户定义的代码或者说Spring替我们实现的代码   
    6.             }   
    7. }  

    在模板中嵌入的是需要客户化的代码,由Spring来作或者需要客户程序亲自动手完成。下面让我们具体看看在JdbcTemplate中的代码是怎样完成使命的,我们举JdbcTemplate.execute()为例,这个方法是在JdbcTemplate中被其他方法调用的基本方法之一,客户程序往往用这个方法来执行基本的SQL语句:
    Java代码 复制代码 收藏代码
    1. public Object execute(ConnectionCallback action) throws DataAccessException {   
    2.     //这里得到数据库联接   
    3.     Connection con = DataSourceUtils.getConnection(getDataSource());   
    4.     try {   
    5.         Connection conToUse = con;   
    6.         //有些特殊的数据库,需要我们使用特别的方法取得datasource   
    7.         if (this.nativeJdbcExtractor != null) {   
    8.             // Extract native JDBC Connection, castable to OracleConnection or the like.   
    9.             conToUse = this.nativeJdbcExtractor.getNativeConnection(con);   
    10.         }   
    11.         else {   
    12.             // Create close-suppressing Connection proxy, also preparing returned Statements.   
    13.             conToUse = createConnectionProxy(con);   
    14.         }   
    15.     //这里调用的是传递进来的匿名类的方法,也就是用户程序需要实现CallBack接口的地方。   
    16.         return action.doInConnection(conToUse);   
    17.     }   
    18.     catch (SQLException ex) {   
    19.         //如果捕捉到数据库异常,把数据库联接释放,同时抛出一个经过Spring转换过的Spring数据库异常,   
    20.         //我们知道,Spring做了一个有意义的工作是把这些数据库异常统一到自己的异常体系里了。   
    21.         DataSourceUtils.releaseConnection(con, getDataSource());   
    22.         con = null;   
    23.         throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);   
    24.     }   
    25.     finally {   
    26.         //最后不管怎样都会把数据库连接释放   
    27.         DataSourceUtils.releaseConnection(con, getDataSource());   
    28.     }   
    29. }  

    对于JdbcTemplate中给出的其他方法,比如query,update,execute等的实现,我们看看query():
    Java代码 复制代码 收藏代码
    1. public Object query(PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse)   
    2.         throws DataAccessException {   
    3.     ..........   
    4.     //这里调用了我们上面看到的execute()基本方法,然而这里的回调实现是Spring为我们完成的查询过程   
    5.     return execute(psc, new PreparedStatementCallback() {   
    6.         public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {   
    7.             //准备查询结果集   
    8.             ResultSet rs = null;   
    9.             try {   
    10.             //这里配置SQL参数   
    11.                 if (pss != null) {   
    12.                     pss.setValues(ps);   
    13.                 }   
    14.           //这里执行的SQL查询   
    15.                 rs = ps.executeQuery();   
    16.                 ResultSet rsToUse = rs;   
    17.                 if (nativeJdbcExtractor != null) {   
    18.                     rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);   
    19.                 }   
    20.          //返回需要的记录集合   
    21.                 return rse.extractData(rsToUse);   
    22.             }   
    23.             finally {   
    24.         //最后关闭查询的纪录集,对数据库连接的释放在execute()中释放,就像我们在上面分析的看到那样。   
    25.                 JdbcUtils.closeResultSet(rs);   
    26.                 if (pss instanceof ParameterDisposer) {   
    27.                     ((ParameterDisposer) pss).cleanupParameters();   
    28.                 }   
    29.             }   
    30.         }   
    31.     });   
    32. }  

    辅助类DataSourceUtils来用来对数据库连接进行管理的主要工具,比如打开和关闭数据库连接等基本操作:
    Java代码 复制代码 收藏代码
    1. public static Connection doGetConnection(DataSource dataSource) throws SQLException {   
    2.    //把对数据库连接放到事务管理里面进行管理   
    3.     ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);   
    4.     if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {   
    5.         conHolder.requested();   
    6.         if (!conHolder.hasConnection()) {   
    7.             logger.debug("Fetching resumed JDBC Connection from DataSource");   
    8.             conHolder.setConnection(dataSource.getConnection());   
    9.         }   
    10.         return conHolder.getConnection();   
    11.     }   
    12.     // 这里得到需要的数据库连接,在配置文件中定义好的。   
    13.     logger.debug("Fetching JDBC Connection from DataSource");   
    14.     Connection con = dataSource.getConnection();   
    15.   
    16.     if (TransactionSynchronizationManager.isSynchronizationActive()) {   
    17.         logger.debug("Registering transaction synchronization for JDBC Connection");   
    18.         // Use same Connection for further JDBC actions within the transaction.   
    19.         // Thread-bound object will get removed by synchronization at transaction completion.   
    20.         ConnectionHolder holderToUse = conHolder;   
    21.         if (holderToUse == null) {   
    22.             holderToUse = new ConnectionHolder(con);   
    23.         }   
    24.         else {   
    25.             holderToUse.setConnection(con);   
    26.         }   
    27.         holderToUse.requested();   
    28.         TransactionSynchronizationManager.registerSynchronization(   
    29.                 new ConnectionSynchronization(holderToUse, dataSource));   
    30.         holderToUse.setSynchronizedWithTransaction(true);   
    31.         if (holderToUse != conHolder) {   
    32.             TransactionSynchronizationManager.bindResource(dataSource, holderToUse);   
    33.         }   
    34.     }   
    35.   
    36.     return con;   
    37. }  

    那我们实际的DataSource对象是怎样得到的?很清楚我们需要在上下文中进行配置:它作为JdbcTemplate父类JdbcAccessor的属性存在:
    Java代码 复制代码 收藏代码
    1. public abstract class JdbcAccessor implements InitializingBean {   
    2.   
    3.     /** 这里是我们依赖注入数据库数据源的地方。 */  
    4.     private DataSource dataSource;   
    5.   
    6.     /** Helper to translate SQL exceptions to DataAccessExceptions */  
    7.     private SQLExceptionTranslator exceptionTranslator;   
    8.   
    9.     private boolean lazyInit = true;   
    10.   
    11.     ........   
    12. }  

    而对于DataSource的缓冲池实现,我们通过定义Apache Jakarta Commons DBCP或者C3P0提供的DataSource来完成,然后只要在上下文中配置好就可以使用了。从上面我们看到JdbcTemplate提供了许多简单查询和更新功能,但是如果需要更高层次的抽象,以及更面向对象的方法来访问数据库。Spring为我们提供了org.springframework.jdbc.object包,这里面包含了SqlQuery,SqlMappingQuery, SqlUpdate和StoredProcedure等类,这些类都是Spring JDBC应用程序可以使用的主要类,但我们要注意使用这些类的时候,用户需要为他们配置好一个JdbcTemplate作为其基本的操作的实现。
    比如说我们使用MappingSqlQuery来将表数据直接映射到一个对象集合 - 具体可以参考书中的例子
    1.我们需要建立DataSource和sql语句并建立持有这些对象的MappingSqlQuery对象
    2.然后我们需要定义传递的SqlParameter,具体的实现我们在MappingSqlQuery的父类RdbmsOperation中可以找到:
    Java代码 复制代码 收藏代码
    1. public void declareParameter(SqlParameter param) throws InvalidDataAccessApiUsageException {   
    2.    //如果声明已经被编译过,则该声明无效   
    3.    if (isCompiled()) {   
    4.        throw new InvalidDataAccessApiUsageException("Cannot add parameters once query is compiled");   
    5.    }   
    6.    //这里对参数值进行声明定义   
    7.    this.declaredParameters.add(param);   

    而这个declareParameters维护的是一个列表:
    Java代码 复制代码 收藏代码
    1. /** List of SqlParameter objects */  
    2. private List declaredParameters = new LinkedList();  

    这个列表在以后compile的过程中会被使用。
    3.然后用户程序需要实现MappingSqlQuery的mapRow接口,将具体的ResultSet数据生成我们需要的对象,这是我们迭代使用的方法。1,2,3步实际上为我们定义好了一个迭代的基本单元作为操作模板。
    4.在应用程序,我们直接调用execute()方法得到我们需要的对象列表,列表中的每一个对象的数据来自于执行SQL语句得到记录集的每一条记录,事实上执行的execute在父类SqlQuery中起作用:
    Java代码 复制代码 收藏代码
    1. public List executeByNamedParam(Map paramMap, Map context) throws DataAccessException {   
    2.     validateNamedParameters(paramMap);   
    3.     Object[] parameters = NamedParameterUtils.buildValueArray(getSql(), paramMap);   
    4.     RowMapper rowMapper = newRowMapper(parameters, context);   
    5.     String sqlToUse = NamedParameterUtils.substituteNamedParameters(getSql(), new MapSqlParameterSource(paramMap));   
    6.     //我们又看到了JdbcTemplate,这里使用JdbcTemplate来完成对数据库的查询操作,所以我们说JdbcTemplate是基本的操作类。   
    7.      return getJdbcTemplate().query(newPreparedStatementCreator(sqlToUse, parameters), rowMapper);   
    8. }  

    在这里我们可以看到template模式的精彩应用和对JdbcTemplate的灵活使用。通过使用它,我们免去了手工迭代ResultSet并将其中的数据转化为对象列表的重复过程。在这里我们只需要定义SQL语句和SqlParameter - 如果需要的话,往往SQL语句就常常能够满足我们的要求了。这是灵活使用JdbcTemplate的一个很好的例子。
    Spring还为其他数据库操作提供了许多服务,比如使用SqlUpdate插入和更新数据库,使用UpdatableSqlQuery更新ResultSet,生成主键,调用存储过程等。
    书中还给出了对BLOB数据和CLOB数据进行数据库操作的例子:
    对BLOB数据的操作通过LobHander来完成,通过调用JdbcTemplate和RDBMS都可以进行操作:
    在JdbcTemplate中,具体的调用可以参考书中的例子 - 是通过以下调用起作用的:
    Java代码 复制代码 收藏代码
    1. public Object execute(String sql, PreparedStatementCallback action) throws DataAccessException {   
    2.     return execute(new SimplePreparedStatementCreator(sql), action);   
    3. }  

    然后通过对实现PreparedStatementCallback接口的AbstractLobCreatingPreparedStatementCallback的回调函数来完成:
    Java代码 复制代码 收藏代码
    1. public final Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {   
    2.     LobCreator lobCreator = this.lobHandler.getLobCreator();   
    3.     try {   
    4.         //这是一个模板方法,具体需要由客户程序实现   
    5.         setValues(ps, lobCreator);   
    6.         return new Integer(ps.executeUpdate());   
    7.     }   
    8.     finally {   
    9.         lobCreator.close();   
    10.     }   
    11. }   
    12. //定义的需要客户程序实现的虚函数   
    13. protected abstract void setValues(PreparedStatement ps, LobCreator lobCreator)   
    14.         throws SQLException, DataAccessException;  

    而我们注意到setValues()是一个需要实现的抽象方法,应用程序通过实现setValues来定义自己的操作 - 在setValues中调用lobCreator.setBlobAsBinaryStrem()。让我们看看具体的BLOB操作在LobCreator是怎样完成的,我们一般使用DefaultLobCreator作为BLOB操作的驱动:
    Java代码 复制代码 收藏代码
    1. public void setBlobAsBinaryStream(   
    2.         PreparedStatement ps, int paramIndex, InputStream binaryStream, int contentLength)   
    3.         throws SQLException {   
    4.     //通过JDBC来完成对BLOB数据的操作,对Oracle,Spring提供了OracleLobHandler来支持BLOB操作。   
    5.     ps.setBinaryStream(paramIndex, binaryStream, contentLength);   
    6.     ........   
    7. }  

    上面提到的是零零碎碎的Spring JDBC使用的例子,可以看到使用Spring JDBC可以帮助我们完成许多数据库的操作。Spring对数据库操作最基本的服务是通过JdbcTeamplate和他常用的回调函数来实现的,在此之上,又提供了许多RMDB的操作来帮助我们更便利的对数据库的数据进行操作 - 注意这里没有引入向Hibernate这样的O/R方案。对这些O/R方案的支持,Spring由其他包来完成服务。
    书中还提到关于execute和update方法之间的区别,update方法返回的是受影响的记录数目的一个计数,并且如果传入参数的话,使用的是java.sql.PreparedStatement,而execute方法总是使用 java.sql.Statement,不接受参数,而且他不返回受影响记录的计数,更适合于创建和丢弃表的语句,而update方法更适合于插入,更新和删除操作,这也是我们在使用时需要注意的。

    posted @ 2011-10-26 23:37 鸿雁 阅读(317) | 评论 (0)编辑 收藏

    Spring源代码解析(二):IoC容器在Web容器中的启动

         摘要: 上面我们分析了IOC容器本身的实现,下面我们看看在典型的web环境中,Spring IOC容器是怎样被载入和起作用的。 简单的说,在web容器中,通过ServletContext为Spring的IOC容器提供宿主环境,对应的建立起一个IOC容器的体系。其中,首先需要建立的是根上下文,这个上下文持有的对象可以有业务对象,数据存取对象,资源,事物管理器等各种中间层对象。在这个上下文的基础上,和web ...  阅读全文

    posted @ 2011-10-26 23:36 鸿雁 阅读(291) | 评论 (0)编辑 收藏

    Spring源代码解析(一):IOC容器

         摘要: 在认真学习Rod.Johnson的三部曲之一:<<Professional Java Development with the spring framework>>,顺便也看了看源代码想知道个究竟,抛砖引玉,有兴趣的同志一起讨论研究吧! 以下内容引自博客:http://jiwenke-spring.blogspot.com/,欢迎指导:) 在Spring中,IOC容器的重要...  阅读全文

    posted @ 2011-10-26 23:35 鸿雁 阅读(166) | 评论 (0)编辑 收藏

    技术选型三个标准必须具备

    众所周知,技术不再是困扰CIO职业前途的必备要件之一。但是,这不是说CIO可以不关心技术了。相反,CIO可以不精通某种技术,但是,一定要对这种技术的优缺点以及发展趋势有个清晰的了解。因为在信息化管理软件选型或者信息化战略制定的时候,还是离不开技术,除了对软件本身进行选型之外,还需要考虑其采用的技术的选型。

        具体的来说,CIO在技术选型上,要考虑所采用的技术是否稳定、扩展性是否较好、跨平台的性能等等。在软件选型的时候,不仅要考虑软件的功能,也要考虑其所采用的技术,只有如此,CIO才能轻松应对后续的变化。

        技术选型标准一:跨平台性

        随着微软打击盗版力度的加强,越来越多的企业开始考虑操作系统的使用成本。确实,对于大部分企业来说,已经习惯使用免费的软件,若让他们掏腰包,去买正版的操作系统与OFFICE办公软件,而且这些费用还不低,他们不一定会肯。随着微软盗版力度的加强,企业一定会千方百计的去寻找微软操作系统与办公软件的替代品。恰好,这个就给开源的LINUX操作系统与OPEOFFICE办公软件提供了发展的机会。

        其实,根据使用经验,这个开源的操作系统与办公软件,其性能方面并不比微软的操作系统与办公软件差。而只是员工以前在学校中学的都是微软的产品,所以,对于LINUX系统上的软件操作并不时很熟悉,所以,会对他有排斥心理。另一方面,开源的LINUX操作系统与办公软件在界面的友好性上可能还是跟微软的产品有所差别,但是,这并不影响实用。而且,LINUX操作系统,在业界的评论上,其稳定性要比微软的操作系统要好,而且,其受到病毒袭击的机率也比微软的操作系统少得多。

        所以,借着微软打击盗版力度的加强,恰好给了一个LINUX系统普及的机会。相信随着企业对于LINUX系统认识的加强,会有越来越多的企业采用这个开源的LINUX系统。

        但是,这对企业或许是一件好事情,可是,对于CIO来说,却是一个头疼的事情。为什么呢?因为现在很多的管理软件,如ERP、财务管理软件等等,都是在微软操作系统的平台上开发的。由于平台开发技术的限制,这些为微软操作系统量身定制的信息化管理系统,无法在LINUX平台上运行顺畅,有的甚至是不兼容的。为此,企业若向放弃使用微软的操作系统,虽然可以节省操作系统上的授权费用,但是,也必将面临着这些管理软件的取舍问题。

        所以,为了我们CIO后续在操作系统的选型上没有这么多的限制,我们从现在开始,就要看到这个趋势,在信息化管理软件选型的时候,要注意软件的跨平台性能。简单地说,我们选择的信息化管理软件不仅要在微软的操作系统上能够跑的顺畅,而且,在LINUX系统平台上也要能够运行顺利,甚至还能够支持苹果等操作系统。只有如此,下次我们在操作系统转型的时候,才不会受到这些信息化管理软件的限制。

        一般来说,现在一些基于JAVA开发的、WEB模式的信息化管理软件,基本上都能够实现多平台的兼容。而且,现在这也是信息化管理软件发展的趋势。以后支持单一平台的信息化管理软件,就像以前只有单一语言的信息化管理软件一样,市场份额将会逐渐缩小。

        技术选型标准二:扩展性与集成性的考虑

        信息化管理系统现在已经发展成为一个体系。为什么这么说呢?现在企业中所采纳的信息化管理系统,已经不是以前单一的一个财务管理软件。如某些企业,已经有了CRM客户关系管理系统、ERP企业资源计划系统、OA办公自动化系统等等。现在要成功实施一个信息化项目,已经不是一件难事,大家都有了丰富的信息化项目实施经验。像以前所说的“上ERP系统是找死,不上ERP系统是等死”的现状已经一去不复返。现在ERP实施的成功率已经非常的高。

        所以,现在信息化项目成功上线已经不是我们CIO所追求的最高目标,而是最基本的目标。现在我们CIO所要考虑的,是系统的集成问题。如何把企业先后实施的各个信息化项目有效的集成起来,减少信息化项目上的重复投资,提高信息化管理的效果,这已经是考验我们CIO能力的一个非常有效的手段。

        现在SOA系统,一个系统集成的平台,在信息化管理中,如一匹黑马,独树一帜。从这里就可以看出,企业对于信息化系统整合的迫切需求。

        为此,我们CIO在信息化管理软件进行选型的时候,就需要考虑这个系统集成性的问题。要考虑我们所即将采用的信息化管理软件,有否提供丰富的接口,可以跟其他信息化管理系统进行交流。

        虽然现在还没有统一的标准,SOA发展也只是起步,没有成熟,还有很多需要改善的地方。但是,我们CIO需要有高瞻远瞩的目光,在信息化管理软件选型的时候,需要关注软件开发商有没有这方面的尝试。如公布自己的接口的详细参数信息,如此的话,即使后面没有统一的集成平台,则通过少量的开发,也可以把两个或者两个以上的操作系统平台整合起来。

        另外关于系统整合还有一个偷懒的方法。就是我们在软件选型的时候,要看看其软件开发商有没有形成一个信息化管理的体系产品。如企业现在可能只需要采用ERP系统,但是我们在CIO选型的时候,需要考虑这个软件开发商是否还开发了CRM系统或者工作六管理系统。因为他们的这些系统之间往往可以实现很好的集成。如此的话,下次若需要其他的信息化管理软件的时候,我们也可以考虑购买他们的产品,如此的话,就可以非常轻松的实现各大信息化管理系统的集成。

        系统的集成是未来信息化管理软件发展的一个趋势,所以,为了后续工作的方便,我们在选择第一个信息化管理软件的时候,就需要有软件集成的思想。只有如此,后续企业需要软件集成的时候,我们才不会手忙脚乱。

        技术选型标准三:技术稳定性的考虑

        软件企业跟生产或者商品企业对于技术的追求可能有所区别。软件企业更多的关注是这个技术会否过时,他们往往希望利用“时髦”来推销他们的新技术产品。而对于生产企业来说,时髦并不是企业所关注的重点,他们更加关注的是这门技术的成熟程度,是否会像微软操作系统一样,隔几天就需要打补丁或者软件升级。

        在我们CIO严重,最适合企业的技术不时往往不是那种刚出炉的技术,虽然他们比较先进,解决了以前产品中的种种缺陷。但是,他们也有一个致命的缺点,就是不够稳定。隔三差五的会出现几个漏洞。而这真是作为CIO最不能够忍受的。因为在企业信息化管理利用中,可能你做的好人家不一定主意,但是,一出现什么问题人家就会抓住你不妨。这大概就叫做“好事不出门、坏事传千里”吧。

        各位CIO在软件选型的时候,考虑技术因素的时候,不要太关注与技术是否够时髦,会不会过时的问题;而应该更多的考虑,这门技术或者产品,是否稳定,用户群是否过大。

        在信息化管理上,要学会模仿,而不是争做“第一个吃螃蟹的人”。在信息化管理行业,变数太多。第一个吃螃蟹的人,大部分是死的比较惨的人。若我们等到别人先试用了之后,产品或者技术被改善的差不多了,然后我们再去采用这种技术或者产品,则就可以最大限度的避免由此带来的风险。

        技术选型有时候比产品功能选型更加难。因为技术选系的话,需要考虑的因素太多,而且现在技术的更新换代实在让人有点目不暇接。现在比较流行的不 B/S模式说不定哪一天也会像以前C/S模式一样被其他模式所代替。而功能选型的话,至少功能是可以收集的,只需要细心与时间,一般不会有多大的误差。

        现在接触的信息化项目越来越多,由此带来的感受也就越来越深。你如果把现在的企业当作临时的中转站,你可能不会有这么大的顾虑,可能会以为只需要考虑未来的几年的时间就可以了。但是,你若想在这家企业中终老,那么,考虑的因素就会比较多,什么软件的集成、软件的扩展性、技术的稳定性与先进行之间的矛盾与取舍,都是我们CIO所需要关注的内容。

        所以,信息化技术日新月异的发展,给我们CIO带来了发展的机遇,但是,也给我们带来的巨大的压力。

    posted @ 2011-10-17 23:30 鸿雁 阅读(331) | 评论 (0)编辑 收藏

    JS技巧代码实例

         摘要: 1.文本框焦点问题onBlur:当失去输入焦点后产生该事件onFocus:当输入获得焦点后,产生该文件Onchange:当文字值改变时,产生该事件Onselect:当文字加亮后,产生该文件<input type="text" value="mm" onfocus="if(value=='mm) {value=''}" onblur="if(value=='') {value='mm'}"&g...  阅读全文

    posted @ 2011-09-08 11:02 鸿雁 阅读(283) | 评论 (0)编辑 收藏

    使用memcached实现session远程分布式存储

    为了使web应用能使用saas模式的大规模访问,必须实现应用的集群部署.要实现集群部署主要需要实现session共享机制,使得多台应用服务器之间会话统一, tomcat等多数服务都采用了session复制技术实现session的共享.

       session复制技术的问题:

       (1)技术复杂,必须在同一种中间件之间完成(如:tomcat-tomcat之间).

       (2)在节点持续增多的情况下,session复制带来的性能损失会快速增加.特别是当session中保存了较大的对象,而且对象变化较快时,性能下降更加显著.这种特性使得web应用的水平扩展受到了限制.

       session共享的另一种思路就是把session集中起来管理,首先想到的是采用数据库来集中存储session,但数据库是文件存储相对内存慢了一个数量级,同时这势必加大数据库系统的负担.所以需要一种既速度快又能远程集中存储的服务,所以就想到了memcached.

      

    memcached是什么?

      memcached是由Danga Interactive开发的,高性能的,分布式的内存对象缓存系统,用于在动态应用中减少数据库负载,提升访问速度。

    memcached能缓存什么?

      通过在内存里维护一个统一的巨大的hash表,Memcached能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。

    memcached快么?

      非常快。memcached使用了libevent(如果可以的话,在linux下使用epoll)来均衡任何数量的打开链接,使用非阻塞的网络I/O,对内部对象实现引用计数(因此,针对多样的客户端,对象可以处在多样的状态), 使用自己的页块分配器和哈希表, 因此虚拟内存不会产生碎片并且虚拟内存分配的时间复杂度可以保证为O(1).。

    Danga Interactive为提升Danga Interactive的速度研发了memcached。目前,LiveJournal.com每天已经在向一百万用户提供多达两千万次的页面访问。而这些,是由一个由web服务器和数据库服务器组成的集群完成的。memcached几乎完全放弃了任何数据都从数据库读取的方式,同时,它还缩短了用户查看页面的速度、更好的资源分配方式,以及memcache失效时对数据库的访问速度。

    memcached的特点

      memcached的缓存是一种分布式的,可以让不同主机上的多个用户同时访问, 因此解决了共享内存只能单机应用的局限,更不会出现使用数据库做类似事情的时候,磁盘开销和阻塞的发生。

      使用memcached来存储session有两种方案:

    (1)直接通过tomcat6的扩展机制实现.

      参考: http://www.javaeye.com/topic/81641

    (2)通过自己编写filter实现.

    考虑到系统的扩展,我们采用这种方案.这样可以使session共享机制和中间件脱钩.

     参考: http://www.javaeye.com/topic/82565

    主要思路:

    (1)继承重构HttpServletRequestWrapper,HttpSessionWrapper类,覆盖原来和session存取相关的方法呢,都通过SessionService类来实现.

    (2)使用filter拦截cookie中的sessionId,通过sessionId构造新的HttpServletRequestWrapper对象,传给后面的应用.

    (3)SessionService连接memcached服务,以sessionId作为key,存取的对象是一个map.map的内容即为session的内容.

    使用过程注意几个问题和改进思路:
    1、memcache的内存应该足够大,这样不会出现用户session从Cache中被清除的问题(可以关闭memcached的对象退出机制)。
    2、如果session的读取比写入要多很多,可以在memcache前再加一个Oscache等本地缓存,减少对memcache的读操作,从而减小网络开销,提高性能。
    3、如果用户非常多,可以使用memcached组,通过set方法中带hashCode,插入到某个memcached服务器

    对于session的清除有几种方案:

    (1)可以在凌晨人最少的时候,对memcached做一次清空。(简单)

    (2)保存在缓存中的对象设置一个失效时间,通过过滤器获取sessionId的值,定期刷新memcached中的对象.长时间没有被刷新的对象自动被清除.(相对复杂,消耗资源)

     

    posted @ 2011-03-29 17:21 鸿雁 阅读(3054) | 评论 (0)编辑 收藏

    双缓冲队列

    提出问题:为啥要有双缓冲队列?
        引用09年9月《程序员》上的一句话:双缓冲队列就是冲着同步/互斥的开销来的。我们知道,在多个线程并发访问同一个资源的时候,需要特别注意线程的同步问题。稍稍不注意,哦活,程序结果不正确了。最经典的就是“银行取钱”的例子,想想,都跟现金挂上钩了,看来这真不容忽视。
        今天我们要谈的不是如何去给资源加锁解锁来解决同步问题,今天的重点在于,如何将线程同步的开销降低到我们力所能及的程度。如果你觉得,你可以通过增加硬件资源来弥补程序开销,那么,你将不可能成为一个优秀的程序员。
        进入正题,先引入一个例子,两个实体:一个是玩具工厂,它的工作就是不停地生产玩具;另外一个实体就是小孩,它的工作就是不停地从工厂拿玩具。小孩不可能直接到工厂去“拿”玩具吧?呵呵,妈妈是绝对不会放心的。所以,我们有一个“搬运工”,搬运工自然要具备“存放”的功能,不然他怎么将玩具带给小孩呢,是吧。所以,我们先将搬运工定义为一个List,用来存放工厂生产出来的玩具。
    代码如下
    玩具类,定义一个玩具实体
    public class Toy {
        private String name;

        public String getName() {
            return name;
        }
    public void setName(String name) {
            this.name = name;
        }
    }

    接下来是玩具工厂,它得“不停地”生产,所以,把它设计成一个线程类

    玩具工厂,将玩具对象,放到list中
    public class Factory extends Thread{
       

        public void run(){
            while(true){
           
            Toy t = new Toy ();
           
            t.setName("玩具");
            synchronized (Tools.lT){
                Tools.lT.add(t);
    }
                try {
                    Thread.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
           
             }
        }
    }
    注意到,在上面这段代码当中,必须得用synchronized (Tools.lT)将Tools.lT给加锁。不然会出现线程同步问题(开销就在这里)。

    再接下来,看看小孩是怎么“玩”玩具的:
    小孩类,从list中取出玩具对象
    public class Kid extends Thread {
        long time1 = System.currentTimeMillis();
        int count = 0;
        public void run() {
            while(true){
           
                synchronized(Tools.lT){
                    if(Tools.lT.size()!=0)
                Tools.lT.remove(0);
                }
                count++;
                if(count==100000){
                    javax.swing.JOptionPane.showMessageDialog(null, "用时间: "+(System.currentTimeMillis()-time1));
                    System.exit(0);
                }
           
            }
            }
    }
    当list不为空的时候,将list中的玩具对象,一个一个取出来,玩完!这个小孩可真够厉害的,呵呵。可以想象为,该类的工作就是不停地向list中取出玩具对象。OK,再来编写方法,如下

    主方法
    public class MainTest {

        /**
         * @param args
         */
        public static void main(String[] args) {
            Factory f = new Factory();
            f.start();
            Kid k = new Kid();
            k.start();
           
        }
    }

    最后是Tools类,他里面有个静态的List对象:
    Tools类
    public class Tools {
        public static List<Toy>lT = new ArrayList<Toy>(10000);
    }

    这样,我们运行一下主方法,看看我们这位“天才”玩完100000个玩具,得花销多少的时间。

     

     


    好,31毫秒。


        这是我们的第一种解决方法,下面再来看第二种解决方法:
    其实我们在Factory类和Kid类中都进行了同步处理,这样一来,浪费了很多时间,到底是不是这样的呢?我们可不可以直接用一个不用处理线程同步的容器来放Toy类对象呢?这样以来是不是就可以节省很多开销了?这个想法是有道理的,但是,事实是不是这样的呢?马上实践!

    代码就不具体贴出来了,只是我们在Tools类中用到的是一个如下的对象
    Public static LinkedBlockingQueue<Toy> lT= new LinkedBlockingQueue<Toy>(1000);

    对,阻塞队列,这样我们就只管往里面取,从里面拿了,不用自己考虑同步问题,Factory类和Kid类中也不同特意去加关键字进行同步了。
    那么这种方案的结果是多少呢?同样是100000个玩具,看结果
      

     

     

     


    哦哦,变成16毫秒了,着实提高了不少效果呢。看来,在处理同步的时候挤时间,是有发展空间的,呵呵。

    等等,有人要发话了,你在这磨叽了半天,还是没有说什么是双缓冲啊,对!有了前面的两种方案,我们再来看看“双缓冲队列”。

    所谓双缓冲队列,自然起码要有两个队列吧,呵呵,在这个例子中,我们可以设计两个List来存放工厂生产出来的玩具对象。
    下面分析一下:
    两个List,一个用来存,一个用来取。有点迷糊?就是有一个listP从工厂那里得到玩具对象,另外一个listT就专门把它得到的玩具对象送去给 Kid类处理。当listT变成空的了以后,再将listP中在这段时间内取到的所有玩具对象放到listT中,好,这完了之后,他们两个就又各自干各自的去了:listP再去取,listT再去送。这样是不是就减少了很多次的线程同步呢?至少,在它们交换之前,listP是完全被工厂类线程占有,listT是完全被Kid类线程占有的,不用处理同步。只有在listT放完了,没得给了,再去跟ListP换过来,这个时候就要处理同步了。

    跟实际联系一下,有两个搬运工A,B,A在工厂,专门从工厂取玩具;B在小孩子身边,专门送玩具给小孩玩。当B身上没有玩具了,自然要回A那里,把A身上的玩具全部拿过来,再来送给小孩玩。在A还有玩具的时候,A和B是在各自的线程里被处理的,即A在拿,B在给。不用担心同步问题。
    这样以来,处理同步问题的次数是不是大大减少了呢?没错,就是这样的。那么怎么跟代码结合呢?
    我们要设计一个监视线程,监视listP是不是空了,要是空了,把它同步起来,把listT也同步起来,让他们交换。完了就各自干各自的了。
    我们来看看这个监视类:

    public class DoubleBufferList {

        private List<Object> lP;
        private List<Object> lT;
        private int gap;

        /**
         * 构造方法
         *
         * @param lP
         *            用来存放对象的队列
         * @param lT
         *            用来取对象的队列
         * @param gap
         *            交换的间隔
         */
        public DoubleBufferList(List lP, List lT, int gap) {
            this.lP = lP;
            this.lT = lT;
            this.gap = gap;
        }

        public void check() {
            Runnable runner = new Runnable() {
                public void run() {
                    while (true) {
                        if (lT.size() == 0) {
                            synchronized (lT) {
                                synchronized (lP) {
                                    lT.addAll(lP);
                                }
                                lP.clear();
                            }
                        }
                        try {
                            Thread.sleep(gap);

                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            Thread thread = new Thread(runner);
            thread.start();
        }

    }


    这个类中的线程方法就是用来交换的,目的就是为了减少处理同步的次数。这种方案中,Facotry类和Kid类跟前面单个List的情况差不多,就不再给出了。只是有一点要注意,Facory类中将玩具对象是放到了lP中,而Kid类中,也只是从lT中去取对象。看看Tools类中,多了一个变量:
    Tools类,声明两个队列
        public static List<Toy>lT = new ArrayList<Toy>(10000);
        public static List<Toy>lP = new ArrayList<Toy>(10000);

    同样是让我们的“天才”玩完100000个玩具,来看看运行需要的时间:

     

     

     


     
    哈哈,似乎跟我们上面的第二种方案,单阻塞队列,没有太大的差异。怎么解释呢?
    不用着急,来,我将额定的玩具量后多加个“0”,让他玩完1000000个!改一下单阻塞队列方案的输出结果,给他们一个标记。再来看看结果:

     
    效果出来了吧,我们再加大量,让他们同时处理10000000个玩具对象: 

     

     

     

    充分说明,使用双缓冲队列,比单缓冲阻塞队列的效果要好,更别说单缓冲队列了。

    总结:
    从上面的分析,我们可以得知,在处理线程同步的时候,是要花费我们的时间的,虽然在有些时候,这样的花费是我们可以接受的,但是在很多情况下,如果我们能注意到这样的浪费,并且及时地完善我们的程序,这样可以更大限度地提高我们程序的运行效率。尤其是在大的程序里面,这样的效果体现得更明显。而往往越大的系统,对性能的要求也就越高。

    posted @ 2011-03-17 16:08 鸿雁 阅读(1239) | 评论 (0)编辑 收藏

    Drawable、Bitmap、byte[]之间的转换 (android转)

    android在处理一写图片资源的时候,会进行一些类型的转换,现在有空整理一下:

    1、Drawable → Bitmap 的简单方法
    ((BitmapDrawable)res.getDrawable(R.drawable.youricon)).getBitmap();


    2、Drawable → Bitmap
    Java代码
    public static Bitmap drawableToBitmap(Drawable drawable) {  
             
            Bitmap bitmap = Bitmap  
                            .createBitmap(  
                                            drawable.getIntrinsicWidth(),  
                                            drawable.getIntrinsicHeight(),  
                                            drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888  
                                                            : Bitmap.Config.RGB_565);  
            Canvas canvas = new Canvas(bitmap);  
            //canvas.setBitmap(bitmap);  
            drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());  
            drawable.draw(canvas);  
            return bitmap;  
    }  


    3.Bitmap→Drawable   的简单方法
    BitmapDrawable bitmapDrawable = (BitmapDrawable)bitmap;     
    Drawable drawable = (Drawable)bitmapDrawable;     
       
       
    Bitmap bitmap = new Bitmap (...);     
    Drawable drawable = new BitmapDrawable(bitmap);   


    3、从资源中获取Bitmap
    Java代码
    Resources res=getResources();  
      
    Bitmap bmp=BitmapFactory.decodeResource(res, R.drawable.pic);  





    4、Bitmap → byte[]
    Java代码
    private byte[] Bitmap2Bytes(Bitmap bm){  
        ByteArrayOutputStream baos = new ByteArrayOutputStream();&nbsp;   
        bm.compress(Bitmap.CompressFormat.PNG, 100, baos);&nbsp;   
        return baos.toByteArray();  
    }  


    5、 byte[] → Bitmap
    Java代码
    private Bitmap Bytes2Bimap(byte[] b){  
                if(b.length!=0){  
                    return BitmapFactory.decodeByteArray(b, 0, b.length);  
                }  
                else {  
                    return null;  
                }  
          }

    posted @ 2011-02-17 09:35 鸿雁 阅读(2213) | 评论 (0)编辑 收藏

    修改TabHost默认样式(文字居中)

    修改TabHost默认样式

    TabHost是Android提供的一个容器组件,利用它可以轻松地实现TAB界面,如下图所示:

    但很多时候,默认的TAB样式并不符合软件的整体风格,这时候该怎么办呢?其实,我们可以编写XML对其样式进行修改。下面修改后的效果图:

    1. TabHost布局文件 main.xml

    	<TabHost
    	    android:id="@+id/tabhost"  
    	    android:layout_width="fill_parent"
    	    android:layout_height="fill_parent">
    <LinearLayout  
    	        android:orientation="vertical"
    	        android:layout_width="fill_parent"  
    	        android:layout_height="fill_parent">
    <TabWidget  
    	            android:id="@android:id/tabs"
    	            android:layout_width="fill_parent" 
    	            android:layout_height="30dip"
    	            android:background="#a0a0a0"
    	            android:layout_weight="0" />
    <FrameLayout  
    	            android:id="@android:id/tabcontent"  
    	            android:layout_width="fill_parent"  
    	            android:layout_height="fill_parent"
    	            android:layout_weight="1">
    <ListView 
    				    android:id="@+id/user_list"
    				    android:layout_width="fill_parent"
    				    android:layout_height="fill_parent" 
    				    android:divider="@drawable/divider_line"
    				    android:cacheColorHint="#00000000" />
    <ListView 
    				    android:id="@+id/article_list"
    				    android:layout_width="fill_parent"
    				    android:layout_height="fill_parent" 
    				    android:divider="@drawable/divider_line"
    				    android:cacheColorHint="#00000000" />
    <ListView 
    				    android:id="@+id/feed_list"
    				    android:layout_width="fill_parent"
    				    android:layout_height="fill_parent" 
    				    android:divider="@drawable/divider_line"
    				    android:cacheColorHint="#00000000" />
    <ListView 
    				    android:id="@+id/book_list"
    				    android:layout_width="fill_parent"
    				    android:layout_height="fill_parent" 
    				    android:divider="@drawable/divider_line"
    				    android:cacheColorHint="#00000000" />
    </FrameLayout>
    </LinearLayout>
    </TabHost>


    FrameLayout里有四个ListView 分别对应用户、文章、频道、图书。
    TabWidget和FrameLayout的ID不能自己定义修改。




    2. Activity后台代码
    RelativeLayout articleTab = (RelativeLayout) LayoutInflater.from(this).inflate(R.layout.minitab, null);
                TextView articleTabLabel = (TextView) articleTab.findViewById(R.id.tab_label);
                articleTabLabel.setText("文章");
                 
                RelativeLayout feedTab = (RelativeLayout) LayoutInflater.from(this).inflate(R.layout.minitab, null);
                TextView feedTabLabel = (TextView) feedTab.findViewById(R.id.tab_label);
                feedTabLabel.setText("频道");
                 
                RelativeLayout bookTab = (RelativeLayout) LayoutInflater.from(this).inflate(R.layout.minitab, null);
                TextView bookTabLabel = (TextView) bookTab.findViewById(R.id.tab_label);
                bookTabLabel.setText("图书");
                 
                TabHost tabHost = (TabHost) findViewById(R.id.tabhost);
                tabHost.setup();
                 
                tabHost.addTab(tabHost.newTabSpec("user").setIndicator(userTab).setContent(R.id.user_list));
                tabHost.addTab(tabHost.newTabSpec("article").setIndicator(articleTab).setContent(R.id.article_list));
                tabHost.addTab(tabHost.newTabSpec("feed").setIndicator(feedTab).setContent(R.id.feed_list));
                tabHost.addTab(tabHost.newTabSpec("book").setIndicator(bookTab).setContent(R.id.book_list));


    TabHost创建出来以后,必须先setup一下,tabHost.setup();

    setIndicator方法设置的View其实就对应了TAB中的一个个选项卡,它们都是通过一个叫minitab的布局文件inflate出来的
    3. 选项卡布局文件minitab.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:paddingLeft="5dip"
        android:paddingRight="5dip">
     
    <TextView android:id="@+id/tab_label"  
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:gravity="center"
            android:textColor="#000000"
            android:textStyle="bold"
            android:background="@drawable/minitab" />
    </RelativeLayout>

    drawable/minitab是一个selector,指定了Tab选项卡的背景颜色。

    4. selector文件 minitab.xml

    <?xml version="1.0" encoding="utf-8"?>
    <selector
    	xmlns:android="http://schemas.android.com/apk/res/android"
    	>
    <item
    		android:state_selected="false"
    		android:drawable="@drawable/minitab_unselected"
    		>
    </item>
    <item
    		android:state_selected="true"
    		android:drawable="@drawable/minitab_default"
    		>
    </item>
    </selector>


    minitab_unselected是一浅蓝色背景图片
    minitab_default是一白色背景图片




    posted @ 2011-02-16 11:28 鸿雁 阅读(5971) | 评论 (0)编辑 收藏

    java规则引擎的原理

    摘 要 Java规则引擎是一种嵌入在Java程序中的组件,它的任务是把当前提交给引擎的Java数据对象与加载在引擎中的业务规则进行测试和比对,激活那些符合当前数据状态下的业务规则,根据业务规则中声明的执行逻辑,触发应用程序中对应的操作。

    引言

    目前,Java社区推动并发展了一种引人注目的新技术——Java规则引擎(Rule Engine)。利用它就可以在应用系统中分离商业决策者的商业决策逻辑和应用开发者的技术决策,并把这些商业决策放在中心数据库或其他统一的地方,让它们能在运行时可以动态地管理和修改,从而为企业保持灵活性和竞争力提供有效的技术支持。

    规则引擎的原理

    1、基于规则的专家系统(RBES)简介

    Java规则引擎起源于基于规则的专家系统,而基于规则的专家系统又是专家系统的其中一个分支。专家系统属于人工智能的范畴,它模仿人类的推理方式,使用试探性的方法进行推理,并使用人类能理解的术语解释和证明它的推理结论。为了更深入地了解Java规则引擎,下面简要地介绍基于规则的专家系统。RBES包括三部分:Rule Base(knowledge base)、Working Memory(fact base)和Inference Engine。它们的结构如下系统所示:





    图1 基于规则的专家系统构成

    如图1所示,推理引擎包括三部分:模式匹配器(Pattern Matcher)、议程(Agenda)和执行引擎(Execution Engine)。推理引擎通过决定哪些规则满足事实或目标,并授予规则优先级,满足事实或目标的规则被加入议程。模式匹配器决定选择执行哪个规则,何时执行规则;议程管理模式匹配器挑选出来的规则的执行次序;执行引擎负责执行规则和其他动作。

    和人类的思维相对应,推理引擎存在两者推理方式:演绎法(Forward-Chaining)和归纳法(Backward-Chaining)。演绎法从一个初始的事实出发,不断地应用规则得出结论(或执行指定的动作)。而归纳法则是根据假设,不断地寻找符合假设的事实。Rete算法是目前效率最高的一个Forward-Chaining推理算法,许多Java规则引擎都是基于Rete算法来进行推理计算的。

    推理引擎的推理步骤如下:

    (1)将初始数据(fact)输入Working Memory。

    (2)使用Pattern Matcher比较规则库(rule base)中的规则(rule)和数据(fact)。

    (3)如果执行规则存在冲突(conflict),即同时激活了多个规则,将冲突的规则放入冲突集合。

    (4)解决冲突,将激活的规则按顺序放入Agenda。

    (5)使用执行引擎执行Agenda中的规则。重复步骤2至5,直到执行完毕所有Agenda中的规则。

    上述即是规则引擎的原始架构,Java规则引擎就是从这一原始架构演变而来的。

    2、规则引擎相关构件

    规则引擎是一种根据规则中包含的指定过滤条件,判断其能否匹配运行时刻的实时条件来执行规则中所规定的动作的引擎。与规则引擎相关的有四个基本概念,为更好地理解规则引擎的工作原理,下面将对这些概念进行逐一介绍。

    1)信息元(Information Unit)

    信息元是规则引擎的基本建筑块,它是一个包含了特定事件的所有信息的对象。这些信息包括:消息、产生事件的应用程序标识、事件产生事件、信息元类型、相关规则集、通用方法、通用属性以及一些系统相关信息等等。

    2)信息服务(Information Services)

    信息服务产生信息元对象。每个信息服务产生它自己类型相对应的信息元对象。即特定信息服务根据信息元所产生每个信息元对象有相同的格式,但可以有不同的属性和规则集。需要注意的是,在一台机器上可以运行许多不同的信息服务,还可以运行同一信息服务的不同实例。但无论如何,每个信息服务只产生它自己类型相对应的信息元。

    3)规则集(Rule Set)

    顾名思义,规则集就是许多规则的集合。每条规则包含一个条件过滤器和多个动作。一个条件过滤器可以包含多个过滤条件。条件过滤器是多个布尔表达式的组合,其组合结果仍然是一个布尔类型的。在程序运行时,动作将会在条件过滤器值为真的情况下执行。除了一般的执行动作,还有三类比较特别的动作,它们分别是:放弃动作(Discard Action)、包含动作(Include Action)和使信息元对象内容持久化的动作。前两种动作类型的区别将在2.3规则引擎工作机制小节介绍。

    4)队列管理器(Queue Manager)

    队列管理器用来管理来自不同信息服务的信息元对象的队列。

    下面将研究规则引擎的这些相关构件是如何协同工作的。

    如图2所示,处理过程分为四个阶段进行:信息服务接受事件并将其转化为信息元,然后这些信息元被传给队列管理器,最后规则引擎接收这些信息元并应用它们自身携带的规则加以执行,直到队列管理器中不再有信息元。





    图2 处理过程协作图

    3、规则引擎的工作机制

    下面专门研究规则引擎的内部处理过程。如图3所示,规则引擎从队列管理器中依次接收信息元,然后依规则的定义顺序检查信息元所带规则集中的规则。如图所示,规则引擎检查第一个规则并对其条件过滤器求值,如果值为假,所有与此规则相关的动作皆被忽略并继续执行下一条规则。如果第二条规则的过滤器值为真,所有与此规则相关的动作皆依定义顺序执行,执行完毕继续下一条规则。该信息元中的所有规则执行完毕后,信息元将被销毁,然后从队列管理器接收下一个信息元。在这个过程中并未考虑两个特殊动作:放弃动作(Discard Action)和包含动作(Include Action)。放弃动作如果被执行,将会跳过其所在信息元中接下来的所有规则,并销毁所在信息元,规则引擎继续接收队列管理器中的下一个信息元。包含动作其实就是动作中包含其它现存规则集的动作。包含动作如果被执行,规则引擎将暂停并进入被包含的规则集,执行完毕后,规则引擎还会返回原来暂停的地方继续执行。这一过程将递归进行。





    图3 规则引擎工作机制

    Java规则引擎的工作机制与上述规则引擎机制十分类似,只不过对上述概念进行了重新包装组合。Java规则引擎对提交给引擎的Java数据对象进行检索,根据这些对象的当前属性值和它们之间的关系,从加载到引擎的规则集中发现符合条件的规则,创建这些规则的执行实例。这些实例将在引擎接到执行指令时、依照某种优先序依次执行。一般来讲,Java规则引擎内部由下面几个部分构成:工作内存(Working Memory)即工作区,用于存放被引擎引用的数据对象集合;规则执行队列,用于存放被激活的规则执行实例;静态规则区,用于存放所有被加载的业务规则,这些规则将按照某种数据结构组织,当工作区中的数据发生改变后,引擎需要迅速根据工作区中的对象现状,调整规则执行队列中的规则执行实例。Java规则引擎的结构示意图如图4所示。





    图4 Java规则引擎工作机制

    当引擎执行时,会根据规则执行队列中的优先顺序逐条执行规则执行实例,由于规则的执行部分可能会改变工作区的数据对象,从而会使队列中的某些规则执行实例因为条件改变而失效,必须从队列中撤销,也可能会激活原来不满足条件的规则,生成新的规则执行实例进入队列。于是就产生了一种“动态”的规则执行链,形成规则的推理机制。这种规则的“链式”反应完全是由工作区中的数据驱动的。

    任何一个规则引擎都需要很好地解决规则的推理机制和规则条件匹配的效率问题。规则条件匹配的效率决定了引擎的性能,引擎需要迅速测试工作区中的数据对象,从加载的规则集中发现符合条件的规则,生成规则执行实例。1982年美国卡耐基·梅隆大学的Charles L. Forgy发明了一种叫Rete算法,很好地解决了这方面的问题。目前世界顶尖的商用业务规则引擎产品基本上都使用Rete算法。

    Java规则引擎API——JSR-94

    为了使规则引擎技术标准化,Java社区制定了Java规则引擎API(JSR94)规范。它为Java平台访问规则引擎定义了一些简单的API。

    Java规则引擎API在javax.rules包中定义,是访问规则引擎的标准企业级API。Java规则引擎API允许客户程序使用统一的方式和不同厂商的规则引擎产品交互,就如同使用JDBC编写独立于厂商访问不同的数据库产品一样。Java规则引擎API包括创建和管理规则集合的机制,在工作区中添加,删除和修改对象的机制,以及初始化,重置和执行规则引擎的机制。

    1、Java规则引擎API体系结构

    Java规则引擎API主要由两大类API组成: 规则管理API(The Rules Administrator API)和运行时客户API(The Runtime Client API)。

    1)规则管理API

    规则管理API在javax.rules.admin中定义,包含装载规则以及与规则对应的动作(执行集 execution sets)以及实例化规则引擎。规则可以从外部资源中装载,比如URI,Input streams, XML streams和readers等等。同时规则管理API还提供了注册和取消注册执行集以及对执行集进行维护的机制。使用admin包定义规则有助于对客户访问运行规则进行控制管理,它通过在执行集上定义许可权使得未经授权的用户无法访问受控规则。

    规则管理API使用类RuleServiceProvider来获得规则管理器(RuleAdministrator)接口的实例。该接口提供方法注册和取消注册执行集。规则管理器提供了本地和远程的RuleExecutionSetProvider,它负责创建规则执行集(RuleExecutionSet)。规则执行集可以从如XML streams, binary streams等来源中创建。这些数据来源及其内容经汇集和序列化后传送到远程的运行规则引擎的服务器上。在大多数应用程序中,远程规则引擎或远程规则数据来源的情况并不多。为了避免这些情况中的网络开销,API规定了可以从运行在同一JVM中规则库中读取数据的本地RuleExecutionSetProvider。规则执行集接口除了拥有能够获得有关规则执行集的方法,还有能够检索在规则执行集中定义的所有规则对象。这使得客户能够知道规则集中的规则对象并且按照自己需要来使用它们。

    2)运行时客户API

    运行时API在javax.rules包中定义,为规则引擎用户运行规则获得结果提供了类和方法。运行时客户只能访问那些使用规则管理API注册过的规则,运行时API帮助用户获得规则会话,并在这个会话中执行规则。

    运行时API提供了对厂商规则引擎API的访问方法,这类似于JDBC。类RuleServiceProvider提供了对具体规则引擎实现的运行时和管理API的访问,规则引擎厂商通过该类将其规则引擎实现提供给客户,并获得RuleServiceProvider唯一标识规则引擎的URL。此URL的标准用法是使用类似于“com.mycompany.myrulesengine.rules.RuleServiceProvider”这样的Internet域名空间,这保证了访问URL的唯一性。类RuleServiceProvider内部实现了规则管理和运行时访问所需的接口。所有的RuleServiceProvider要想被客户所访问都必须用RuleServiceProviderManager进行注册,注册方式类似于JDBC API的DriverManager和Driver。

    运行时接口是运行时API的关键部分。运行时接口提供了用于创建规则会话(RuleSession)的方法,规则会话是用来运行规则的。运行时API同时也提供了访问在service provider注册过的所有规则执行集(RuleExecutionSets)。规则会话接口定义了客户使用的会话的类型,客户根据自己运行规则的方式可以选择使用有状态会话或者无状态会话。无状态会话的工作方式就像一个无状态会话bean。客户可以发送单个输入对象或一列对象来获得输出对象。当客户需要一个与规则引擎间的专用会话时,有状态会话就很有用。输入的对象通过addObject() 方法可以加入到会话当中。同一个会话当中可以加入多个对象。对话中已有对象可以通过使用updateObject()方法得到更新。只要客户与规则引擎间的会话依然存在,会话中的对象就不会丢失。

    RuleExecutionSetMetaData接口提供给客户让其查找规则执行集的元数据(metadata)。元数据通过规则会话接口(RuleSession Interface)提供给用户。

    2、Java规则引擎API安全问题

    规则引擎API将管理API和运行时API加以分开,从而为这些包提供了较好粒度的安全控制。规则引擎API并没有提供明显的安全机制,它可以和J2EE规范中定义的标准安全API联合使用。安全可以由以下机制提供,如Java 认证和授权服务 (JAAS),Java加密扩展(JCE),Java安全套接字扩展(JSSE),或者其它定制的安全API。使用JAAS可以定义规则执行集的许可权限,从而只有授权用户才能访问。

    3、异常与日志

    规则引擎API定义了javax.rules.RuleException作为规则引擎异常层次的根类。所有其它异常都继承于这个根类。规则引擎中定义的异常都是受控制的异常(checked exceptions),所以捕获异常的任务就交给了规则引擎。规则引擎API没有提供明确的日志机制,但是它建议将Java Logging API用于规则引擎API。

    JSR 94 为规则引擎提供了公用标准API,仅仅为实现规则管理API和运行时API提供了指导规范,并没有提供规则和动作该如何定义以及该用什么语言定义规则,也没有为规则引擎如何读和评价规则提供技术性指导。

    结束语

    规则引擎技术为管理多变的业务逻辑提供了一种解决方案。规则引擎既可以管理应用层的业务逻辑又可以使表示层的页面流程可订制。这就给软件架构师设计大型信息系统提供了一项新的选择。而Java规则引擎在Java社区制定标准规范以后必将获得更大发展。

    Drools规则引擎初学入门实例HelloWorld
    (1)下载eclipse(www.eclipse.org),如果是一般的java开发,下载Eclipse IDE for Java Developers就行了,解压后即可使用;

    (2)下载Drools(http://jboss.org/drools/downloads.html),目前最新版本是Drools 4.0.7 Binaries,下载后解压即可;

    (3)之后下载eclipse的Drools插件,版本跟eclipse对应,目前有Drools 4.0.7 Eclipse 3.2 Workbench和Drools 4.0.7 Eclipse Europa 3.3 Workbench两种。

             Drools插件解压后,将里面的org.drools.eclipse_4.0.7.jar文件copy到eclipse的plugins目录中,重启eclipse,在工具栏可以看到一个  图标,这就是Drools的工作台,之后就可通过这个按钮创建Drools resource文件了。

    (4)开始Hello World

    Java文件:DroolsTest.java

    package com.sample;

    import java.io.InputStreamReader;
    import java.io.Reader;

    import org.drools.RuleBase;
    import org.drools.RuleBaseFactory;
    import org.drools.WorkingMemory;
    import org.drools.compiler.PackageBuilder;
    import org.drools.rule.Package;

    /**
    * This is a sample file to launch a rule package from a rule source file.
    */
    public class DroolsTest {

        public static final void main(String[] args) {
            try {
             
             //load up the rulebase
                 RuleBase ruleBase = readRule();
                 WorkingMemory workingMemory = ruleBase.newStatefulSession();
                
                //go !
                 Message message = new Message();
                 message.setMessage(  "Hello World" );
                 message.setStatus( Message.HELLO );
                 workingMemory.insert( message );
                 workingMemory.fireAllRules();   
                
                
             } catch (Throwable t) {
                 t.printStackTrace();
             }
         }

        /**
          * Please note that this is the "low level" rule assembly API.
          */
    private static RuleBase readRule() throws Exception {
      //read in the source
       Reader source = new InputStreamReader( DroolsTest.class.getResourceAsStream( "/Sample.drl" ) );
      
      //optionally read in the DSL (if you are using it).
      //Reader dsl = new InputStreamReader( DroolsTest.class.getResourceAsStream( "/mylang.dsl" ) );

      //Use package builder to build up a rule package.
      //An alternative lower level class called "DrlParser" can also be used...
      
       PackageBuilder builder = new PackageBuilder();

      //this wil parse and compile in one step
      //NOTE: There are 2 methods here, the one argument one is for normal DRL.
       builder.addPackageFromDrl( source );

      //Use the following instead of above if you are using a DSL:
      //builder.addPackageFromDrl( source, dsl );
      
      //get the compiled package (which is serializable)
       Package pkg = builder.getPackage();
      
      //add the package to a rulebase (deploy the rule package).
       RuleBase ruleBase = RuleBaseFactory.newRuleBase();
       ruleBase.addPackage( pkg );
      return ruleBase;
    }

    public static class Message {
      public static final int HELLO = 0;
      public static final int GOODBYE = 1;
      public static final int GAME_OVER = 2;
      
      private String message;
      
      private int status;
      
      public String getMessage() {
       return this.message;
       }
      
      public void setMessage(String message) {
       this.message = message;
       }
      
      public int getStatus() {
       return this.status;
       }
      
      public void setStatus( int status ) {
       this.status = status;
       }
    }
        
    }
    选择插件Drools按钮里的"New Rule resource",建立规则(rule)文件:Sample.drl

    package com.sample

    import com.sample.DroolsTest.Message;

    rule "Hello World"
    when
       m : Message( status == Message.HELLO, message : message )
    then
       System.out.println( message );
       m.setMessage( "Goodbye cruel world" );
       m.setStatus( Message.GOODBYE );
       update( m );
    end

    rule "GoodBye"
    no-loop true
    when
       m : Message( status == Message.GOODBYE, message : message )
    then
       System.out.println( message );
       m.setStatus(Message.GAME_OVER);
       m.setMessage("game over now!");
       update( m );
    end

    rule "game over"
    when
       m : Message( status == Message.GAME_OVER)
    then
       System.out.println( m.getMessage() );   
    end
    注意:文件要放在相应的包里,然后编译—执行,当时出现了错误,查找资料,还需要加载包,包括:

    <1> Drools 4.0.7目录下的drools-core-4.0.7.jar,drools-compiler-4.0.7.jar

    <2> Drools 4.0.7\lib目录下的antlr-runtime-3.0.jar,mvel-1.3.1-java1.4.jar

    <3>以及eclipse\plugins目录下的org.eclipse.jdt.core_3.2.3.v_686_R32x.jar(不同版本,包名会稍有不同)。

    重新运行,应该就不会有错了。执行结果如下:

    Hello World
    Goodbye cruel world
    game over now!

    posted @ 2011-01-25 23:27 鸿雁 阅读(293) | 评论 (0)编辑 收藏

    特殊字符的转义


  • 特殊字符转义   
  •   
  • 由于 Web 应用程序需要联合使用到多种语言,每种语言都包含一些特殊的字符,对于动态语言或标签式的语言而言,如果需要动态构造语言的内容时,一个我们经常会碰到的问题就是特殊字符转义的问题。下面是 Web 开发者最常面对需要转义的特殊字符类型:   
  •   
  • HTML 特殊字符;    
  • JavaScript 特殊字符;    
  • SQL 特殊字符;    
  • 如果不对这些特殊字符进行转义处理,则不但可能破坏文档结构,还可以引发潜在的安全问题。Spring 为 HTML 和 JavaScript 特殊字符提供了转义操作工具类,它们分别是 HtmlUtils 和 JavaScriptUtils。   
  •   
  • HTML 特殊字符转义   
  •   
  • HTML 中 <,>,& 等字符有特殊含义,它们是 HTML 语言的保留字,因此不能直接使用。使用这些个字符时,应使用它们的转义序列:   
  •   
  • &:&    
  • " :"    
  • < :<    
  • > :>    
  • 由于 HTML 网页本身就是一个文本型结构化文档,如果直接将这些包含了 HTML 特殊字符的内容输出到网页中,极有可能破坏整个 HTML 文档的结构。所以,一般情况下需要对动态数据进行转义处理,使用转义序列表示 HTML 特殊字符。下面的 JSP 网页将一些变量动态输出到 HTML 网页中:   
  •   
  •   
  • 清单 1. 未进行 HTML 特殊字符转义处理网页   
  •                    
  • <%@ page language="java" contentType="text/html; charset=utf-8"%>   
  • <%!   
  •    String userName = "</td><tr></table>";   
  •    String address = " \" type=\"button";   
  •  %>   
  • <table border="1">   
  •    <tr>   
  •      <td>姓名:</td><td><%=userName%></td> ①   
  •    </tr>   
  •    <tr>   
  •      <td>年龄:</td><td>28</td>   
  •    </tr>   
  • </table>   
  •  <input value="<%=address%>"  type="text" /> ②   
  •     
  •   
  •   
  • 在 ① 和 ② 处,我们未经任何转义处理就直接将变量输出到 HTML 网页中,由于这些变量可能包含一些特殊的 HTML 的字符,它们将可能破坏整个 HTML 文档的结构。我们可以从以上 JSP 页面的一个具体输出中了解这一问题:   
  •   
  • <table border="1">   
  •    <tr>   
  •      <td>姓名:</td><td></td><tr></table></td>    
  •      ① 破坏了 <table> 的结构   
  •    </tr>   
  •    <tr>   
  •      <td>年龄:</td><td>28</td>   
  •    </tr>   
  • </table>   
  •  <input value=" " type="button"  type="text" />    
  •  ② 将本来是输入框组件偷梁换柱为按钮组件   
  •     
  •   
  •   
  • 融合动态数据后的 HTML 网页已经面目全非,首先 ① 处的 <table> 结构被包含 HTML 特殊字符的 userName 变量截断了,造成其后的 <table> 代码变成无效的内容;其次,② 处 <input> 被动态数据改换为按钮类型的组件(type="button")。为了避免这一问题,我们需要事先对可能破坏 HTML 文档结构的动态数据进行转义处理。Spring 为我们提供了一个简单适用的 HTML 特殊字符转义工具类,它就是 HtmlUtils。下面,我们通过一个简单的例子了解 HtmlUtils 的具体用法:   
  •   
  •   
  • 清单 2. HtmpEscapeExample   
  •                    
  • package com.baobaotao.escape;   
  • import org.springframework.web.util.HtmlUtils;   
  • public class HtmpEscapeExample {   
  •     public static void main(String[] args) {   
  •         String specialStr = "<div id=\"testDiv\">test1;test2</div>";   
  •         String str1 = HtmlUtils.htmlEscape(specialStr); ①转换为HTML转义字符表示   
  •         System.out.println(str1);   
  •           
  •         String str2 = HtmlUtils.htmlEscapeDecimal(specialStr); ②转换为数据转义表示   
  •         System.out.println(str2);   
  •           
  •         String str3 = HtmlUtils.htmlEscapeHex(specialStr); ③转换为十六进制数据转义表示   
  •         System.out.println(str3);   
  •           
  •         ④下面对转义后字符串进行反向操作   
  •         System.out.println(HtmlUtils.htmlUnescape(str1));   
  •         System.out.println(HtmlUtils.htmlUnescape(str2));   
  •         System.out.println(HtmlUtils.htmlUnescape(str3));   
  •     }   
  • }   
  •     
  •   
  •   
  • HTML 不但可以使用通用的转义序列表示 HTML 特殊字符,还可以使用以 # 为前缀的数字序列表示 HTML 特殊字符,它们在最终的显示效果上是一样的。HtmlUtils 提供了三个转义方法:   
  •   
  • 方法 说明    
  • static String htmlEscape(String input)  将 HTML 特殊字符转义为 HTML 通用转义序列;    
  • static String htmlEscapeDecimal(String input)  将 HTML 特殊字符转义为带 # 的十进制数据转义序列;    
  • static String htmlEscapeHex(String input)  将 HTML 特殊字符转义为带 # 的十六进制数据转义序列;    
  •   
  • 此外,HtmlUtils 还提供了一个能够将经过转义内容还原的方法:htmlUnescape(String input),它可以还原以上三种转义序列的内容。运行以上代码,您将可以看到以下的输出:   
  •   
  • str1:<div id="testDiv">test1;test2</div>   
  • str2:<div id="testDiv">test1;test2</div>   
  • str3:<div id="testDiv">test1;test2</div>   
  • <div id="testDiv">test1;test2</div>   
  • <div id="testDiv">test1;test2</div>   
  • <div id="testDiv">test1;test2</div>   
  •     
  •   
  •   
  • 您只要使用 HtmlUtils 对代码 清单 1 的 userName 和 address 进行转义处理,最终输出的 HTML 页面就不会遭受破坏了。   
  •   
  • JavaScript 特殊字符转义   
  •   
  • JavaScript 中也有一些需要特殊处理的字符,如果直接将它们嵌入 JavaScript 代码中,JavaScript 程序结构将会遭受破坏,甚至被嵌入一些恶意的程序。下面列出了需要转义的特殊 JavaScript 字符:   
  •   
  • ' :\'    
  • " :\"    
  • \ :\\    
  • 走纸换页: \f    
  • 换行:\n    
  • 换栏符:\t    
  • 回车:\r    
  • 回退符:\b    
  •     
  • 我们通过一个具体例子演示动态变量是如何对 JavaScript 程序进行破坏的。假设我们有一个 JavaScript 数组变量,其元素值通过一个 Java List 对象提供,下面是完成这一操作的 JSP 代码片断:   
  •   
  •   
  • 清单 3. jsTest.jsp:未对 JavaScript 特殊字符进行处理   
  •                    
  • <%@ page language="java" contentType="text/html; charset=utf-8"%>   
  • <jsp:directive.page import="java.util.*"/>   
  • <%   
  •   List textList = new ArrayList();   
  •   textList.add("\";alert();j=\"");   
  • %>   
  • <script>   
  •   var txtList = new Array();   
  •    <% for ( int i = 0 ; i < textList.size() ; i++) { %>   
  •      txtList[<%=i%>] = "<%=textList.get(i)%>";    
  •      ① 未对可能包含特殊 JavaScript 字符的变量进行处理   
  •    <% } %>   
  • </script>   
  •     
  •   
  •   
  • 当客户端调用这个 JSP 页面后,将得到以下的 HTML 输出页面:   
  •   
  • <script>   
  •   var txtList = new Array();   
  •    txtList[0] = "";alert();j=""; ① 本来是希望接受一个字符串,结果被植入了一段JavaScript代码   
  • </script>   
  •     
  •   
  •   
  • 由于包含 JavaScript 特殊字符的 Java 变量直接合并到 JavaScript 代码中,我们本来期望 ① 处所示部分是一个普通的字符串,但结果变成了一段 JavaScript 代码,网页将弹出一个 alert 窗口。想像一下如果粗体部分的字符串是“";while(true)alert();j="”时会产生什么后果呢?   
  •   
  • 因此,如果网页中的 JavaScript 代码需要通过拼接 Java 变量动态产生时,一般需要对变量的内容进行转义处理,可以通过 Spring 的 JavaScriptUtils 完成这件工作。下面,我们使用 JavaScriptUtils 对以上代码进行改造:   
  •   
  • <%@ page language="java" contentType="text/html; charset=utf-8"%>   
  • <jsp:directive.page import="java.util.*"/>   
  • <jsp:directive.page import="org.springframework.web.util.JavaScriptUtils"/>   
  • <%   
  •   List textList = new ArrayList();   
  •   textList.add("\";alert();j=\"");   
  • %>   
  • <script>   
  •    var txtList = new Array();   
  •    <% for ( int i = 0 ; i < textList.size() ; i++) { %>   
  •    ① 在输出动态内容前事先进行转义处理   
  •    txtList[<%=i%>] = "<%=JavaScriptUtils.javaScriptEscape(""+textList.get(i))%>";   
  •    <% } %>   
  • </script>   
  •     
  •   
  •   
  • 通过转义处理后,这个 JSP 页面输出的结果网页的 JavaScript 代码就不会产生问题了:   
  •   
  • <script>   
  •    var txtList = new Array();   
  •    txtList[0] = "\";alert();j=\"";   
  •    ① 粗体部分仅是一个普通的字符串,而非一段 JavaScript 的语句了   
  • </script>   
  •     
  •   
  •   
  • SQL特殊字符转义   
  •   
  • 应该说,您即使没有处理 HTML 或 JavaScript 的特殊字符,也不会带来灾难性的后果,但是如果不在动态构造 SQL 语句时对变量中特殊字符进行处理,将可能导致程序漏洞、数据盗取、数据破坏等严重的安全问题。网络中有大量讲解 SQL 注入的文章,感兴趣的读者可以搜索相关的资料深入研究。   
  •   
  • 虽然 SQL 注入的后果很严重,但是只要对动态构造的 SQL 语句的变量进行特殊字符转义处理,就可以避免这一问题的发生了。来看一个存在安全漏洞的经典例子:   
  •   
  • SELECT COUNT(userId)    
  • FROM t_user    
  • WHERE userName='"+userName+"' AND password ='"+password+"';   
  •     
  •   
  •   
  • 以上 SQL 语句根据返回的结果数判断用户提供的登录信息是否正确,如果 userName 变量不经过特殊字符转义处理就直接合并到 SQL 语句中,黑客就可以通过将 userName 设置为 “1' or '1'='1”绕过用户名/密码的检查直接进入系统了。   
  •   
  • 所以除非必要,一般建议通过 PreparedStatement 参数绑定的方式构造动态 SQL 语句,因为这种方式可以避免 SQL 注入的潜在安全问题。但是往往很难在应用中完全避免通过拼接字符串构造动态 SQL 语句的方式。为了防止他人使用特殊 SQL 字符破坏 SQL 的语句结构或植入恶意操作,必须在变量拼接到 SQL 语句之前对其中的特殊字符进行转义处理。Spring 并没有提供相应的工具类,您可以通过 jakarta commons lang 通用类包中(spring/lib/jakarta-commons/commons-lang.jar)的 StringEscapeUtils 完成这一工作:   
  •   
  •   
  • 清单 4. SqlEscapeExample   
  •                    
  • package com.baobaotao.escape;   
  • import org.apache.commons.lang.StringEscapeUtils;   
  • public class SqlEscapeExample {   
  •     public static void main(String[] args) {   
  •         String userName = "1' or '1'='1";   
  •         String password = "123456";   
  •         userName = StringEscapeUtils.escapeSql(userName);   
  •         password = StringEscapeUtils.escapeSql(password);   
  •         String sql = "SELECT COUNT(userId) FROM t_user WHERE userName='"  
  •             + userName + "' AND password ='" + password + "'";   
  •         System.out.println(sql);   
  •     }   
  • }   
  •     
  •   
  •   
  • 事实上,StringEscapeUtils 不但提供了 SQL 特殊字符转义处理的功能,还提供了 HTML、XML、JavaScript、Java 特殊字符的转义和还原的方法。如果您不介意引入 jakarta commons lang 类包,我们更推荐您使用 StringEscapeUtils 工具类完成特殊字符转义处理的工作。   

  • posted @ 2011-01-24 14:16 鸿雁 阅读(3838) | 评论 (0)编辑 收藏

    Oracle JOB间隔时间详解

    Oracle JOB 间隔时间详解
    INTERVAL参数设置:
    每天运行一次                        'SYSDATE + 1'
    每小时运行一次                     'SYSDATE + 1/24'
    每10分钟运行一次                 'SYSDATE + 10/(60*24)'
    每30秒运行一次                    'SYSDATE + 30/(60*24*60)'
    每隔一星期运行一次               'SYSDATE + 7'
    每个月最后一天运行一次         'TRUNC(LAST_DAY(ADD_MONTHS(SYSDATE,1))) + 23/24'
    每年1月1号零时                    'TRUNC(LAST_DAY(TO_DATE(EXTRACT(YEAR from SYSDATE)||'12'||'01','YYYY-MM-DD'))+1)'
    每天午夜12点                       'TRUNC(SYSDATE + 1)'
    每天早上8点30分                  'TRUNC(SYSDATE + 1) + (8*60+30)/(24*60)'
    每星期二中午12点                 'NEXT_DAY(TRUNC(SYSDATE ), ''TUESDAY'' ) + 12/24'
    每个月第一天的午夜12点        'TRUNC(LAST_DAY(SYSDATE ) + 1)'
    每个月最后一天的23点           'TRUNC (LAST_DAY (SYSDATE)) + 23 / 24'
    每个季度最后一天的晚上11点  'TRUNC(ADD_MONTHS(SYSDATE + 2/24, 3 ), 'Q' ) -1/24'
    每星期六和日早上6点10分      'TRUNC(LEAST(NEXT_DAY(SYSDATE, ''S ......

    posted @ 2010-12-24 10:18 鸿雁 阅读(999) | 评论 (0)编辑 收藏

    JVM调优总结 -Xms -Xmx -Xmn -Xss(转)

    1. 堆大小设置
      JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。
      典型设置:
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
        -
        Xmx3550m:设置JVM最大可用内存为3550M。
        -Xms3550m
        :设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
        -Xmn2g
        :设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
        -Xss128k
        :设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
      • java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
        -XX:NewRatio=4
        :设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
        -XX:SurvivorRatio=4
        :设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
        -XX:MaxPermSize=16m:设置持久代大小为16m。
        -XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
    2. 回收器选择
      JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。
      1. 吞吐量优先的并行收集器
        如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。
        典型配置
        • java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
          -XX:+UseParallelGC
          :选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
          -XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
        • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
          -XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。
        • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC  -XX:MaxGCPauseMillis=100
          -XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
        • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC  -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
          -XX:+UseAdaptiveSizePolicy
          :设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
      2. 响应时间优先的并发收集器
        如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。
        典型配置
        • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
          -XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
          -XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
        • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
          -XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
          -XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片
    3. 辅助信息
      JVM提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:
      • -XX:+PrintGC
        输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]

                        [Full GC 121376K->10414K(130112K), 0.0650971 secs]

      • -XX:+PrintGCDetails
        输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]

                        [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]

      • -XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps可与上面两个混合使用
        输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
      • -XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用
        输出形式:Application time: 0.5291524 seconds
      • -XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间。可与上面混合使用
        输出形式:Total time for which application threads were stopped: 0.0468229 seconds
      • -XX:PrintHeapAtGC:打印GC前后的详细堆栈信息
        输出形式:
        34.702: [GC {Heap before gc invocations=7:
         def new generation   total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)
        eden space 49152K,  99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)
        from space 6144K,  55% used [0x221d0000, 0x22527e10, 0x227d0000)
          to   space 6144K,   0% used [0x21bd0000, 0x21bd0000, 0x221d0000)
         tenured generation   total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)
        the space 69632K,   3% used [0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)
         compacting perm gen  total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
           the space 8192K,  35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
            ro space 8192K,  66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
            rw space 12288K,  46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
        34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:
         def new generation   total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)
        eden space 49152K,   0% used [0x1ebd0000, 0x1ebd0000, 0x21bd0000)
          from space 6144K,  55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)
          to   space 6144K,   0% used [0x221d0000, 0x221d0000, 0x227d0000)
         tenured generation   total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)
        the space 69632K,   4% used [0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)
         compacting perm gen  total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
           the space 8192K,  35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
            ro space 8192K,  66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
            rw space 12288K,  46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
        }
        , 0.0757599 secs]
      • -Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析。
    4. 常见配置汇总
      1. 堆设置
        • -Xms:初始堆大小
        • -Xmx:最大堆大小
        • -XX:NewSize=n:设置年轻代大小
        • -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
        • -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
        • -XX:MaxPermSize=n:设置持久代大小
      2. 收集器设置
        • -XX:+UseSerialGC:设置串行收集器
        • -XX:+UseParallelGC:设置并行收集器
        • -XX:+UseParalledlOldGC:设置并行年老代收集器
        • -XX:+UseConcMarkSweepGC:设置并发收集器
      3. 垃圾回收统计信息
        • -XX:+PrintGC
        • -XX:+PrintGCDetails
        • -XX:+PrintGCTimeStamps
        • -Xloggc:filename
      4. 并行收集器设置
        • -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
        • -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
        • -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
      5. 并发收集器设置
        • -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
        • -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。


    四、调优总结

    1. 年轻代大小选择
      • 响应时间优先的应用尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
      • 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
    2. 年老代大小选择
      • 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
        • 并发垃圾收集信息
        • 持久代并发收集次数
        • 传统GC信息
        • 花在年轻代和年老代回收上的时间比例
        减少年轻代和年老代花费的时间,一般会提高应用的效率
      • 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
    3. 较小堆引起的碎片问题
      因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
      • -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
      • -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩

    posted @ 2010-08-23 10:27 鸿雁 阅读(278) | 评论 (0)编辑 收藏

    JVM调优总结(一)-- 一些概念(转)

     

    数据类型

        Java虚拟机中,数据类型可以分为两类:基本类型引用类型。基本类型的变量保存原始值,即:他代表的值就是数值本身;而引用类型的变量保存引用值。“引用值”代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。

    基本类型包括:byte,short,int,long,char,float,double,Boolean,returnAddress

    引用类型包括:类类型接口类型数组

    堆与栈

        堆和栈是程序运行的关键,很有必要把他们的关系说清楚。

     

       

        栈是运行时的单位,而堆是存储的单位

        栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放、放在哪儿。

        在Java中一个线程就会相应有一个线程栈与之对应,这点很容易理解,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。而堆则是所有线程共享的。栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等;而堆只负责存储对象信息。

        为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗

        第一,从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。

        第二,堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。

        第三,栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。

        第四,面向对象就是堆和栈的完美结合。其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是,面向对象的引入,使得对待问题的思考方式发生了改变,而更接近于自然方式的思考。当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在堆中;而对象的行为(方法),就是运行逻辑,放在栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美。

        在Java中,Main函数就是栈的起始点,也是程序的起始点

        程序要运行总是有一个起点的。同C语言一样,java中的Main就是那个起点。无论什么java程序,找到main就找到了程序执行的入口:)

        堆中存什么?栈中存什么

        堆中存的是对象。栈中存的是基本数据类型堆中对象的引用。一个对象的大小是不可估计的,或者说是可以动态变化的,但是在栈中,一个对象只对应了一个4btye的引用(堆栈分离的好处:))。

        为什么不把基本类型放堆中呢?因为其占用的空间一般是1~8个字节——需要空间比较少,而且因为是基本类型,所以不会出现动态增长的情况——长度固定,因此栈中存储就够了,如果把他存在堆中是没有什么意义的(还会浪费空间,后面说明)。可以这么说,基本类型和对象的引用都是存放在栈中,而且都是几个字节的一个数,因此在程序运行时,他们的处理方式是统一的。但是基本类型、对象引用和对象本身就有所区别了,因为一个是栈中的数据一个是堆中的数据。最常见的一个问题就是,Java中参数传递时的问题。

        Java中的参数传递时传值呢?还是传引用

        要说明这个问题,先要明确两点:

             1. 不要试图与C进行类比,Java中没有指针的概念

             2. 程序运行永远都是在栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题。不会直接传对象本身。

        明确以上两点后。Java在方法调用传递参数时,因为没有指针,所以它都是进行传值调用(这点可以参考C的传值调用)。因此,很多书里面都说Java是进行传值调用,这点没有问题,而且也简化的C中复杂性。

        但是传引用的错觉是如何造成的呢?在运行栈中,基本类型和引用的处理是一样的,都是传值,所以,如果是传引用的方法调用,也同时可以理解为“传引用值”的传值调用,即引用的处理跟基本类型是完全一样的。但是当进入被调用方法时,被传递的这个引用的值,被程序解释(或者查找)到堆中的对象,这个时候才对应到真正的对象。如果此时进行修改,修改的是引用对应的对象,而不是引用本身,即:修改的是堆中的数据。所以这个修改是可以保持的了。

        对象,从某种意义上说,是由基本类型组成的。可以把一个对象看作为一棵树,对象的属性如果还是对象,则还是一颗树(即非叶子节点),基本类型则为树的叶子节点。程序参数传递时,被传递的值本身都是不能进行修改的,但是,如果这个值是一个非叶子节点(即一个对象引用),则可以修改这个节点下面的所有内容。

     

        堆和栈中,栈是程序运行最根本的东西。程序运行可以没有堆,但是不能没有栈。而堆是为栈进行数据存储服务,说白了堆就是一块共享的内存。不过,正是因为堆和栈的分离的思想,才使得Java的垃圾回收成为可能。

         Java中,栈的大小通过-Xss来设置,当栈中存储数据比较多时,需要适当调大这个值,否则会出现java.lang.StackOverflowError异常。常见的出现这个异常的是无法返回的递归,因为此时栈中保存的信息都是方法返回的记录点。

    posted @ 2010-08-23 10:25 鸿雁 阅读(184) | 评论 (0)编辑 收藏

    百万数据查询优化技巧三十则

            1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。

      2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

      select id from t where num is null

      可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:

      select id from t where num=0

      3.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。

      4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:

      select id from t where num=10 or num=20

      可以这样查询:

      select id from t where num=10

      union all

      select id from t where num=20

      5.in 和 not in 也要慎用,否则会导致全表扫描,如:

      select id from t where num in(1,2,3)

      对于连续的数值,能用 between 就不要用 in 了:

      select id from t where num between 1 and 3

      6.下面的查询也将导致全表扫描:

      select id from t where name like '%abc%'

      若要提高效率,可以考虑全文检索。

      7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:

      select id from t where num=@num

      可以改为强制查询使用索引:

      select id from t with(index(索引名)) where num=@num

      8.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:

      select id from t where num/2=100

      应改为:

      select id from t where num=100*2

      9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:

      select id from t where substring(name,1,3)='abc'--name以abc开头的id

      select id from t where datediff(day,createdate,'2005-11-30')=0--‘2005-11-30’生成的id

      应改为:

      select id from t where name like 'abc%'

      select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'

      10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

      11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。

      12.不要写一些没有意义的查询,如需要生成一个空表结构:

      select col1,col2 into #t from t where 1=0

      这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:

      create table #t(...)

      13.很多时候用 exists 代替 in 是一个好的选择:

      select num from a where num in(select num from b)

      用下面的语句替换:

      select num from a where exists(select 1 from b where num=a.num)

      14.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。

      15.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。

      16.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。

      17.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

      18.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

      19.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。

      20.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。

      21.避免频繁创建和删除临时表,以减少系统表资源的消耗。

      22.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。

      23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。

      24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。

      25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。

      26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。

      27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。

      28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。

      29.尽量避免大事务操作,提高系统并发能力。

      30.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

    posted @ 2010-07-16 16:04 鸿雁 阅读(243) | 评论 (0)编辑 收藏

    Spring多数据源解决方案

    在很多大型应用中都会对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效提高系统的水平伸缩性。而这样的方案就会不同于常见的单一数据实例的方案,这就要程序在运行时根据当时的请求及系统状态来动态的决定将数据存储在哪个数据库实例中,以及从哪个数据库提取数据。

     

    Figure 1 数据分割及多数据库架构

        通常这种多数据源的逻辑会渗透到业务逻辑中,同时也会给我们使用的数据访问API诸如Hibernate和iBatis等带来不便(需要指定多个SessionFactory或SqlMapClient实例来对应多个DataSource)。


    Figure 2 多数据源的选择逻辑渗透至客户端

     

        解决方案


    Figure 3 采用Proxy模式来封装数据源选择逻辑

        通过采用Proxy模式我们在方案中实现一个虚拟的数据源,并且用它来封装数据源选择逻辑,这样就可以有效地将数据源选择逻辑从Client中分离出来。

        Client提供选择所需的上下文(因为这是Client所知道的),由虚拟的DataSource根据Client提供的上下文来实现数据源的选择。

        Spring2.x的版本中提供了实现这种方式的基本框架,虚拟的DataSource仅需继承AbstractRoutingDataSource实现determineCurrentLookupKey()在其中封装数据源的选择逻辑。

        实例:

    publicclass DynamicDataSource extends AbstractRoutingDataSource {

          static Logger log = Logger.getLogger("DynamicDataSource");

          @Override

          protected Object determineCurrentLookupKey() {

                String userId=(String)DbContextHolder.getContext();

                Integer dataSourceId=getDataSourceIdByUserId(userId);

                return dataSourceId;

          }

    }

        实例中通过UserId来决定数据存放在哪个数据库中。

        配置文件示例:

    <bean id="dataSource" class="com.bitfone.smartdm.datasource.DynamicDataSource">

                  <property name="targetDataSources">

                     <map key-type="java.lang.Integer">

                        <entry key="0" value-ref="dataSource0"/>

                        <entry key="1" value-ref="dataSource1"/>

                        <entry key="2" value-ref="dataSource2"/>

                     </map>

                  </property>

                  <property name="defaultTargetDataSource" ref="dataSource0"/>

                </bean>

                <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">

                    <property name="configLocation" value="classpath:com/bitfone/smartdm/dao/sqlmap/sql-map-config.xml"/>

                    <property name="dataSource" ref="dataSource"/>

               </bean>

                <bean id="UserInfoDAO" class="com.bitfone.smartdm.dao.impl.UserInfoDAO">

     

                      <property name="sqlMapClient" ref="sqlMapClient"/>

                </bean>

    posted @ 2010-07-14 17:10 鸿雁 阅读(122) | 评论 (0)编辑 收藏

    使用JAVA中的动态代理实现数据库连接池(转自developerWorks )

         摘要:   通过使用JAVA中的动态代理实现数据库连接池,使使用者可以以普通的jdbc连接的使用习惯来使用连接池。 数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的连接数据库对服务性能来讲是一个瓶颈,使用缓冲池技术可以来消除这个瓶颈。我们可以在互联网上找到很多关于数据库连接池的源程序,但是都发现这样一个共同的问题:这些连接池的实现方法都不同程度地增加了与使用者之间的耦合度。很多的连...  阅读全文

    posted @ 2010-07-14 17:08 鸿雁 阅读(141) | 评论 (0)编辑 收藏

    jfreechart 生成折线图,饼图,柱状图,堆栈柱状图(转自javaEye)

    1.所需包
    (1) jfreechart-1.0.8a.jar
    (2) jcommon-1.0.12.jar
    2.运行环境
    JDK 1.5

    3.源代码

    import java.awt.Color;
    import java.awt.Font;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.text.DecimalFormat;
    import java.text.NumberFormat;

    import org.jfree.chart.ChartFactory;
    import org.jfree.chart.ChartUtilities;
    import org.jfree.chart.JFreeChart;
    import org.jfree.chart.axis.CategoryAxis;
    import org.jfree.chart.axis.CategoryLabelPositions;
    import org.jfree.chart.axis.NumberAxis;
    import org.jfree.chart.axis.ValueAxis;
    import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
    import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
    import org.jfree.chart.plot.CategoryPlot;
    import org.jfree.chart.plot.PiePlot3D;
    import org.jfree.chart.plot.PlotOrientation;
    import org.jfree.chart.renderer.category.BarRenderer;
    import org.jfree.chart.renderer.category.LineAndShapeRenderer;
    import org.jfree.chart.renderer.category.StackedBarRenderer;
    import org.jfree.chart.title.TextTitle;
    import org.jfree.data.category.CategoryDataset;
    import org.jfree.data.general.DatasetUtilities;
    import org.jfree.data.general.DefaultPieDataset;
    import org.jfree.data.general.PieDataset;

    /**
    * 实际取色的时候一定要16位的,这样比较准确
    *
    * @author new
    */
    public class CreateChartServiceImpl
    {
    private static final String CHART_PATH = "E:/test/";

    public static void main(String[] args)
    {
    // TODO Auto-generated method stub
    CreateChartServiceImpl pm = new CreateChartServiceImpl();
    // 生成饼状图
    pm.makePieChart();
    // 生成单组柱状图
    pm.makeBarChart();
    // 生成多组柱状图
    pm.makeBarGroupChart();
    // 生成堆积柱状图
    pm.makeStackedBarChart();
    // 生成折线图
    pm.makeLineAndShapeChart();
    }

    /**
    * 生成折线图
    */
    public void makeLineAndShapeChart()
    {
    double[][] data = new double[][]
    {
    { 672, 766, 223, 540, 126 },
    { 325, 521, 210, 340, 106 },
    { 332, 256, 523, 240, 526 } };
    String[] rowKeys =
    { "苹果", "梨子", "葡萄" };
    String[] columnKeys =
    { "北京", "上海", "广州", "成都", "深圳" };
    CategoryDataset dataset = getBarData(data, rowKeys, columnKeys);
    createTimeXYChar("折线图", "x轴", "y轴", dataset, "lineAndShap.png");
    }

    /**
    * 生成分组的柱状图
    */
    public void makeBarGroupChart()
    {
    double[][] data = new double[][]
    {
    { 672, 766, 223, 540, 126 },
    { 325, 521, 210, 340, 106 },
    { 332, 256, 523, 240, 526 } };
    String[] rowKeys =
    { "苹果", "梨子", "葡萄" };
    String[] columnKeys =
    { "北京", "上海", "广州", "成都", "深圳" };
    CategoryDataset dataset = getBarData(data, rowKeys, columnKeys);
    createBarChart(dataset, "x坐标", "y坐标", "柱状图", "barGroup.png");
    }

    /**
    * 生成柱状图
    */
    public void makeBarChart()
    {
    double[][] data = new double[][]
    {
    { 672, 766, 223, 540, 126 } };
    String[] rowKeys =
    { "苹果" };
    String[] columnKeys =
    { "北京", "上海", "广州", "成都", "深圳" };
    CategoryDataset dataset = getBarData(data, rowKeys, columnKeys);
    createBarChart(dataset, "x坐标", "y坐标", "柱状图", "bar.png");
    }

    /**
    * 生成堆栈柱状图
    */
    public void makeStackedBarChart()
    {
    double[][] data = new double[][]
    {
    { 0.21, 0.66, 0.23, 0.40, 0.26 },
    { 0.25, 0.21, 0.10, 0.40, 0.16 } };
    String[] rowKeys =
    { "苹果", "梨子" };
    String[] columnKeys =
    { "北京", "上海", "广州", "成都", "深圳" };
    CategoryDataset dataset = getBarData(data, rowKeys, columnKeys);
    createStackedBarChart(dataset, "x坐标", "y坐标", "柱状图", "stsckedBar.png");
    }

    /**
    * 生成饼状图
    */
    public void makePieChart()
    {
    double[] data =
    { 9, 91 };
    String[] keys =
    { "失败率", "成功率" };

    createValidityComparePimChar(getDataPieSetByUtil(data, keys), "饼状图",
            "pie2.png", keys);
    }

    // 柱状图,折线图 数据集
    public CategoryDataset getBarData(double[][] data, String[] rowKeys,
            String[] columnKeys)
    {
    return DatasetUtilities
            .createCategoryDataset(rowKeys, columnKeys, data);

    }

    // 饼状图 数据集
    public PieDataset getDataPieSetByUtil(double[] data,
            String[] datadescription)
    {

    if (data != null && datadescription != null)
    {
    if (data.length == datadescription.length)
    {
    DefaultPieDataset dataset = new DefaultPieDataset();
    for (int i = 0; i < data.length; i++)
    {
    dataset.setValue(datadescription[i], data[i]);
    }
    return dataset;
    }

    }

    return null;
    }

    /**
    * 柱状图
    *
    *@param dataset 数据集
    * @param xName x轴的说明(如种类,时间等)
    * @param yName y轴的说明(如速度,时间等)
    * @param chartTitle 图标题
    * @param charName 生成图片的名字
    * @return
    */
    public String createBarChart(CategoryDataset dataset, String xName,
            String yName, String chartTitle, String charName)
    {
    JFreeChart chart = ChartFactory.createBarChart(chartTitle, // 图表标题
            xName, // 目录轴的显示标签
            yName, // 数值轴的显示标签
            dataset, // 数据集
            PlotOrientation.VERTICAL, // 图表方向:水平、垂直
            true, // 是否显示图例(对于简单的柱状图必须是false)
            false, // 是否生成工具
            false // 是否生成URL链接
            );
    Font labelFont = new Font("SansSerif", Font.TRUETYPE_FONT, 12);
    /*
    * VALUE_TEXT_ANTIALIAS_OFF表示将文字的抗锯齿关闭,
    * 使用的关闭抗锯齿后,字体尽量选择12到14号的宋体字,这样文字最清晰好看
    */
    // chart.getRenderingHints().put(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
    chart.setTextAntiAlias(false);
    chart.setBackgroundPaint(Color.white);
    // create plot
    CategoryPlot plot = chart.getCategoryPlot();
    // 设置横虚线可见
    plot.setRangeGridlinesVisible(true);
    // 虚线色彩
    plot.setRangeGridlinePaint(Color.gray);

    // 数据轴精度
    NumberAxis vn = (NumberAxis) plot.getRangeAxis();
    // vn.setAutoRangeIncludesZero(true);
    DecimalFormat df = new DecimalFormat("#0.00");
    vn.setNumberFormatOverride(df); // 数据轴数据标签的显示格式
    // x轴设置
    CategoryAxis domainAxis = plot.getDomainAxis();
    domainAxis.setLabelFont(labelFont);// 轴标题
    domainAxis.setTickLabelFont(labelFont);// 轴数值

    // Lable(Math.PI/3.0)度倾斜
    // domainAxis.setCategoryLabelPositions(CategoryLabelPositions
    // .createUpRotationLabelPositions(Math.PI / 3.0));

    domainAxis.setMaximumCategoryLabelWidthRatio(0.6f);// 横轴上的 Lable 是否完整显示

    // 设置距离图片左端距离
    domainAxis.setLowerMargin(0.1);
    // 设置距离图片右端距离
    domainAxis.setUpperMargin(0.1);
    // 设置 columnKey 是否间隔显示
    // domainAxis.setSkipCategoryLabelsToFit(true);

    plot.setDomainAxis(domainAxis);
    // 设置柱图背景色(注意,系统取色的时候要使用16位的模式来查看颜色编码,这样比较准确)
    plot.setBackgroundPaint(new Color(255, 255, 204));

    // y轴设置
    ValueAxis rangeAxis = plot.getRangeAxis();
    rangeAxis.setLabelFont(labelFont);
    rangeAxis.setTickLabelFont(labelFont);
    // 设置最高的一个 Item 与图片顶端的距离
    rangeAxis.setUpperMargin(0.15);
    // 设置最低的一个 Item 与图片底端的距离
    rangeAxis.setLowerMargin(0.15);
    plot.setRangeAxis(rangeAxis);

    BarRenderer renderer = new BarRenderer();
    // 设置柱子宽度
    renderer.setMaximumBarWidth(0.05);
    // 设置柱子高度
    renderer.setMinimumBarLength(0.2);
    // 设置柱子边框颜色
    renderer.setBaseOutlinePaint(Color.BLACK);
    // 设置柱子边框可见
    renderer.setDrawBarOutline(true);

    // // 设置柱的颜色
    renderer.setSeriesPaint(0, new Color(204, 255, 255));
    renderer.setSeriesPaint(1, new Color(153, 204, 255));
    renderer.setSeriesPaint(2, new Color(51, 204, 204));

    // 设置每个地区所包含的平行柱的之间距离
    renderer.setItemMargin(0.0);

    // 显示每个柱的数值,并修改该数值的字体属性
    renderer.setIncludeBaseInRange(true);
    renderer
            .setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
    renderer.setBaseItemLabelsVisible(true);

    plot.setRenderer(renderer);
    // 设置柱的透明度
    plot.setForegroundAlpha(1.0f);

    FileOutputStream fos_jpg = null;
    try
    {
    isChartPathExist(CHART_PATH);
    String chartName = CHART_PATH + charName;
    fos_jpg = new FileOutputStream(chartName);
    ChartUtilities.writeChartAsPNG(fos_jpg, chart, 500, 500, true, 10);
    return chartName;
    }
    catch (Exception e)
    {
    e.printStackTrace();
    return null;
    }
    finally
    {
    try
    {
    fos_jpg.close();
    }
    catch (Exception e)
    {
    e.printStackTrace();
    }
    }
    }

    /**
    * 横向图
    *
    * @param dataset 数据集
    * @param xName x轴的说明(如种类,时间等)
    * @param yName y轴的说明(如速度,时间等)
    * @param chartTitle 图标题
    * @param charName 生成图片的名字
    * @return
    */
    public String createHorizontalBarChart(CategoryDataset dataset,
            String xName, String yName, String chartTitle, String charName)
    {
    JFreeChart chart = ChartFactory.createBarChart(chartTitle, // 图表标题
            xName, // 目录轴的显示标签
            yName, // 数值轴的显示标签
            dataset, // 数据集
            PlotOrientation.VERTICAL, // 图表方向:水平、垂直
            true, // 是否显示图例(对于简单的柱状图必须是false)
            false, // 是否生成工具
            false // 是否生成URL链接
            );

    CategoryPlot plot = chart.getCategoryPlot();
    // 数据轴精度
    NumberAxis vn = (NumberAxis) plot.getRangeAxis();
    //设置刻度必须从0开始
    // vn.setAutoRangeIncludesZero(true);
    DecimalFormat df = new DecimalFormat("#0.00");
    vn.setNumberFormatOverride(df); // 数据轴数据标签的显示格式

    CategoryAxis domainAxis = plot.getDomainAxis();

    domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_45); // 横轴上的
    // Lable
    Font labelFont = new Font("SansSerif", Font.TRUETYPE_FONT, 12);

    domainAxis.setLabelFont(labelFont);// 轴标题
    domainAxis.setTickLabelFont(labelFont);// 轴数值

    domainAxis.setMaximumCategoryLabelWidthRatio(0.8f);// 横轴上的 Lable 是否完整显示
    // domainAxis.setVerticalCategoryLabels(false);
    plot.setDomainAxis(domainAxis);

    ValueAxis rangeAxis = plot.getRangeAxis();
    // 设置最高的一个 Item 与图片顶端的距离
    rangeAxis.setUpperMargin(0.15);
    // 设置最低的一个 Item 与图片底端的距离
    rangeAxis.setLowerMargin(0.15);
    plot.setRangeAxis(rangeAxis);
    BarRenderer renderer = new BarRenderer();
    // 设置柱子宽度
    renderer.setMaximumBarWidth(0.03);
    // 设置柱子高度
    renderer.setMinimumBarLength(30);

    renderer.setBaseOutlinePaint(Color.BLACK);

    // 设置柱的颜色
    renderer.setSeriesPaint(0, Color.GREEN);
    renderer.setSeriesPaint(1, new Color(0, 0, 255));
    // 设置每个地区所包含的平行柱的之间距离
    renderer.setItemMargin(0.5);
    // 显示每个柱的数值,并修改该数值的字体属性
    renderer
            .setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
    // 设置柱的数值可见
    renderer.setBaseItemLabelsVisible(true);

    plot.setRenderer(renderer);
    // 设置柱的透明度
    plot.setForegroundAlpha(0.6f);

    FileOutputStream fos_jpg = null;
    try
    {
    isChartPathExist(CHART_PATH);
    String chartName = CHART_PATH + charName;
    fos_jpg = new FileOutputStream(chartName);
    ChartUtilities.writeChartAsPNG(fos_jpg, chart, 500, 500, true, 10);
    return chartName;
    }
    catch (Exception e)
    {
    e.printStackTrace();
    return null;
    }
    finally
    {
    try
    {
    fos_jpg.close();
    }
    catch (Exception e)
    {
    e.printStackTrace();
    }
    }
    }

    /**
    * 饼状图
    *
    * @param dataset 数据集
    * @param chartTitle 图标题
    * @param charName 生成图的名字
    * @param pieKeys 分饼的名字集
    * @return
    */
    public String createValidityComparePimChar(PieDataset dataset,
            String chartTitle, String charName, String[] pieKeys)
    {
    JFreeChart chart = ChartFactory.createPieChart3D(chartTitle, // chart
            // title
            dataset,// data
            true,// include legend
            true, false);

    // 使下说明标签字体清晰,去锯齿类似于
    // chart.getRenderingHints().put(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);的效果
    chart.setTextAntiAlias(false);
    // 图片背景色
    chart.setBackgroundPaint(Color.white);
    // 设置图标题的字体重新设置title
    Font font = new Font("隶书", Font.BOLD, 25);
    TextTitle title = new TextTitle(chartTitle);
    title.setFont(font);
    chart.setTitle(title);

    PiePlot3D plot = (PiePlot3D) chart.getPlot();
    // 图片中显示百分比:默认方式

    // 指定饼图轮廓线的颜色
    // plot.setBaseSectionOutlinePaint(Color.BLACK);
    // plot.setBaseSectionPaint(Color.BLACK);

    // 设置无数据时的信息
    plot.setNoDataMessage("无对应的数据,请重新查询。");

    // 设置无数据时的信息显示颜色
    plot.setNoDataMessagePaint(Color.red);

    // 图片中显示百分比:自定义方式,{0} 表示选项, {1} 表示数值, {2} 表示所占比例 ,小数点后两位
    plot.setLabelGenerator(new StandardPieSectionLabelGenerator(
            "{0}={1}({2})", NumberFormat.getNumberInstance(),
            new DecimalFormat("0.00%")));
    // 图例显示百分比:自定义方式, {0} 表示选项, {1} 表示数值, {2} 表示所占比例
    plot.setLegendLabelGenerator(new StandardPieSectionLabelGenerator(
            "{0}={1}({2})"));

    plot.setLabelFont(new Font("SansSerif", Font.TRUETYPE_FONT, 12));

    // 指定图片的透明度(0.0-1.0)
    plot.setForegroundAlpha(0.65f);
    // 指定显示的饼图上圆形(false)还椭圆形(true)
    plot.setCircular(false, true);

    // 设置第一个 饼块section 的开始位置,默认是12点钟方向
    plot.setStartAngle(90);

    // // 设置分饼颜色
    plot.setSectionPaint(pieKeys[0], new Color(244, 194, 144));
    plot.setSectionPaint(pieKeys[1], new Color(144, 233, 144));

    FileOutputStream fos_jpg = null;
    try
    {
    // 文件夹不存在则创建
    isChartPathExist(CHART_PATH);
    String chartName = CHART_PATH + charName;
    fos_jpg = new FileOutputStream(chartName);
    // 高宽的设置影响椭圆饼图的形状
    ChartUtilities.writeChartAsPNG(fos_jpg, chart, 500, 230);

    return chartName;
    }
    catch (Exception e)
    {
    e.printStackTrace();
    return null;
    }
    finally
    {
    try
    {
    fos_jpg.close();
    System.out.println("create pie-chart.");
    }
    catch (Exception e)
    {
    e.printStackTrace();
    }
    }

    }

    /**
    * 判断文件夹是否存在,如果不存在则新建
    * @param chartPath
    */
    private void isChartPathExist(String chartPath)
    {
    File file = new File(chartPath);
    if (!file.exists())
    {
    file.mkdirs();
    // log.info("CHART_PATH="+CHART_PATH+"create.");
    }
    }

    /**
    * 折线图
    *
    * @param chartTitle
    * @param x
    * @param y
    * @param xyDataset
    * @param charName
    * @return
    */
    public String createTimeXYChar(String chartTitle, String x, String y,
            CategoryDataset xyDataset, String charName)
    {

    JFreeChart chart = ChartFactory.createLineChart(chartTitle, x, y,
            xyDataset, PlotOrientation.VERTICAL, true, true, false);

    chart.setTextAntiAlias(false);
    chart.setBackgroundPaint(Color.WHITE);
    // 设置图标题的字体重新设置title
    Font font = new Font("隶书", Font.BOLD, 25);
    TextTitle title = new TextTitle(chartTitle);
    title.setFont(font);
    chart.setTitle(title);
    // 设置面板字体
    Font labelFont = new Font("SansSerif", Font.TRUETYPE_FONT, 12);

    chart.setBackgroundPaint(Color.WHITE);

    CategoryPlot categoryplot = (CategoryPlot) chart.getPlot();
    // x轴 // 分类轴网格是否可见
    categoryplot.setDomainGridlinesVisible(true);
    // y轴 //数据轴网格是否可见
    categoryplot.setRangeGridlinesVisible(true);

    categoryplot.setRangeGridlinePaint(Color.WHITE);// 虚线色彩

    categoryplot.setDomainGridlinePaint(Color.WHITE);// 虚线色彩

    categoryplot.setBackgroundPaint(Color.lightGray);

    // 设置轴和面板之间的距离
    // categoryplot.setAxisOffset(new RectangleInsets(5D, 5D, 5D, 5D));

    CategoryAxis domainAxis = categoryplot.getDomainAxis();

    domainAxis.setLabelFont(labelFont);// 轴标题
    domainAxis.setTickLabelFont(labelFont);// 轴数值

    domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_45); // 横轴上的
    // Lable
    // 45度倾斜
    // 设置距离图片左端距离
    domainAxis.setLowerMargin(0.0);
    // 设置距离图片右端距离
    domainAxis.setUpperMargin(0.0);

    NumberAxis numberaxis = (NumberAxis) categoryplot.getRangeAxis();
    numberaxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
    numberaxis.setAutoRangeIncludesZero(true);

    // 获得renderer 注意这里是下嗍造型到lineandshaperenderer!!
    LineAndShapeRenderer lineandshaperenderer = (LineAndShapeRenderer) categoryplot
            .getRenderer();

    lineandshaperenderer.setBaseShapesVisible(true); // series 点(即数据点)可见
    lineandshaperenderer.setBaseLinesVisible(true); // series 点(即数据点)间有连线可见

    // 显示折点数据
    // lineandshaperenderer.setBaseItemLabelGenerator(new
    // StandardCategoryItemLabelGenerator());
    // lineandshaperenderer.setBaseItemLabelsVisible(true);

    FileOutputStream fos_jpg = null;
    try
    {
    isChartPathExist(CHART_PATH);
    String chartName = CHART_PATH + charName;
    fos_jpg = new FileOutputStream(chartName);

    // 将报表保存为png文件
    ChartUtilities.writeChartAsPNG(fos_jpg, chart, 500, 510);

    return chartName;
    }
    catch (Exception e)
    {
    e.printStackTrace();
    return null;
    }
    finally
    {
    try
    {
    fos_jpg.close();
    System.out.println("create time-createTimeXYChar.");
    }
    catch (Exception e)
    {
    e.printStackTrace();
    }
    }
    }

    /**
    * 堆栈柱状图
    *
    * @param dataset
    * @param xName
    * @param yName
    * @param chartTitle
    * @param charName
    * @return
    */
    public String createStackedBarChart(CategoryDataset dataset, String xName,
            String yName, String chartTitle, String charName)
    {
    // 1:得到 CategoryDataset

    // 2:JFreeChart对象
    JFreeChart chart = ChartFactory.createStackedBarChart(chartTitle, // 图表标题
            xName, // 目录轴的显示标签
            yName, // 数值轴的显示标签
            dataset, // 数据集
            PlotOrientation.VERTICAL, // 图表方向:水平、垂直
            true, // 是否显示图例(对于简单的柱状图必须是false)
            false, // 是否生成工具
            false // 是否生成URL链接
            );
    // 图例字体清晰
    chart.setTextAntiAlias(false);

    chart.setBackgroundPaint(Color.WHITE);

    // 2 .2 主标题对象 主标题对象是 TextTitle 类型
    chart
            .setTitle(new TextTitle(chartTitle, new Font("隶书", Font.BOLD,
                    25)));
    // 2 .2.1:设置中文
    // x,y轴坐标字体
    Font labelFont = new Font("SansSerif", Font.TRUETYPE_FONT, 12);

    // 2 .3 Plot 对象 Plot 对象是图形的绘制结构对象
    CategoryPlot plot = chart.getCategoryPlot();

    // 设置横虚线可见
    plot.setRangeGridlinesVisible(true);
    // 虚线色彩
    plot.setRangeGridlinePaint(Color.gray);

    // 数据轴精度
    NumberAxis vn = (NumberAxis) plot.getRangeAxis();
    // 设置最大值是1
    vn.setUpperBound(1);
    // 设置数据轴坐标从0开始
    // vn.setAutoRangeIncludesZero(true);
    // 数据显示格式是百分比
    DecimalFormat df = new DecimalFormat("0.00%");
    vn.setNumberFormatOverride(df); // 数据轴数据标签的显示格式
    // DomainAxis (区域轴,相当于 x 轴), RangeAxis (范围轴,相当于 y 轴)
    CategoryAxis domainAxis = plot.getDomainAxis();

    domainAxis.setLabelFont(labelFont);// 轴标题
    domainAxis.setTickLabelFont(labelFont);// 轴数值

    // x轴坐标太长,建议设置倾斜,如下两种方式选其一,两种效果相同
    // 倾斜(1)横轴上的 Lable 45度倾斜
    // domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_45);
    // 倾斜(2)Lable(Math.PI 3.0)度倾斜
    // domainAxis.setCategoryLabelPositions(CategoryLabelPositions
    // .createUpRotationLabelPositions(Math.PI / 3.0));

    domainAxis.setMaximumCategoryLabelWidthRatio(0.6f);// 横轴上的 Lable 是否完整显示

    plot.setDomainAxis(domainAxis);

    // y轴设置
    ValueAxis rangeAxis = plot.getRangeAxis();
    rangeAxis.setLabelFont(labelFont);
    rangeAxis.setTickLabelFont(labelFont);
    // 设置最高的一个 Item 与图片顶端的距离
    rangeAxis.setUpperMargin(0.15);
    // 设置最低的一个 Item 与图片底端的距离
    rangeAxis.setLowerMargin(0.15);
    plot.setRangeAxis(rangeAxis);

    // Renderer 对象是图形的绘制单元
    StackedBarRenderer renderer = new StackedBarRenderer();
    // 设置柱子宽度
    renderer.setMaximumBarWidth(0.05);
    // 设置柱子高度
    renderer.setMinimumBarLength(0.1);
            //设置柱的边框颜色
    renderer.setBaseOutlinePaint(Color.BLACK);
    //设置柱的边框可见
    renderer.setDrawBarOutline(true);

    // // 设置柱的颜色(可设定也可默认)
    renderer.setSeriesPaint(0, new Color(204, 255, 204));
    renderer.setSeriesPaint(1, new Color(255, 204, 153));

    // 设置每个地区所包含的平行柱的之间距离
    renderer.setItemMargin(0.4);

    plot.setRenderer(renderer);
    // 设置柱的透明度(如果是3D的必须设置才能达到立体效果,如果是2D的设置则使颜色变淡)
    // plot.setForegroundAlpha(0.65f);

    FileOutputStream fos_jpg = null;
    try
    {
    isChartPathExist(CHART_PATH);
    String chartName = CHART_PATH + charName;
    fos_jpg = new FileOutputStream(chartName);
    ChartUtilities.writeChartAsPNG(fos_jpg, chart, 500, 500, true, 10);
    return chartName;
    }
    catch (Exception e)
    {
    e.printStackTrace();
    return null;
    }
    finally
    {
    try
    {
    fos_jpg.close();
    }
    catch (Exception e)
    {
    e.printStackTrace();
    }
    }
    }

    }

    posted @ 2009-11-12 15:25 鸿雁 阅读(940) | 评论 (0)编辑 收藏

    阿里要走102年 阿里的工程师能走多远?(转)

    很高兴看到阿里云的成立。这意味着阿里已经把对互联网技术的投入提高到了的战略高度。过去经常听工程师抱怨阿里不是一家技术公司。现在再没有理由可以这样抱怨了。但是要实现这个战略,没有技术储备是不行的。招聘和培养工程师显然是目前集团各子公司同时面临的一个令人头痛的难题。

    由于曾经在硅谷工作过,我常想,为什么硅谷有这么多40岁以上的工程师,而国内30岁以上的就已经寥寥无几了?为什么硅谷的工程师的技术寿命可以这么长?为什么他们可以不浮躁,不急功近利呢?阿里要走102年,阿里的工程师可以一起走多远呢?

    在国内,有2-3年工作经历的工程师就可以算有经验的了。工作了5年以上的工程师往往会考虑向管理岗位转型,或者向业务转型。中国目前处于高度发展的阶段。很多企业缺乏管理人才,工作5年就被提吧为干部很正常。但留下的后遗症是30岁以上的优秀技术人才极度缺乏。

    在硅谷,5年以下工作经验的人都算是初级的。一般高级工程师需要5年以上的工作经验,架构师一般需要10年以上的工作经验。这还不算上大部分硅谷的工程师都有计算机硕士学位。毕业的时候一般已经是24,25岁了。再工作10年,35岁才升为架构师是非常正常的。然而,公司里的架构师有限。其实大部分 40岁的工程师仍然在一线工作,比如写程序,做测试,进行项目管理等。

    美国硅谷是计算机人才集中的地方,也是创业公司群集的地方。在硅谷,从只有几个人到几十个人的创业公司比比皆是。他们的共同梦想就是经过几年的奋斗,通过技术的创新,再次缔造像英特尔,苹果,思科,甲骨文,雅虎,Google,Facebook等这样的神话。即使创造不了神话,也可以通过IPO或者被收购的途径创造财富。在这样的环境中,公司对管理人才的需求同样是非常大的,但为什么仍然有大量的工程师“无动于衷”,仍然从事着技术活儿呢?

    我认为有两个主要原因。

    一个是外因。在美国,管理岗位的待遇和技术岗位待遇相差不大。特别在崇尚技术的硅谷,经理的地位并不比工程师高,甚至更低。比如架构师在公司里的重要性往往要超过经理。因此管理岗位的“诱惑”并不大。在这样一种技术氛围中,走技术路线很正常。

    但是即使在这样一个技术环境中,硅谷对管理人才依然需要。当工程师表现出色时,也有很多机会转成管理岗位。然而相当一部分工程师会主动放弃这样的机会,而继续干他们的技术活儿。这就是内因在驱动了。技术工作和管理工作的本质区别是,前者面对的是系统(软件,硬件等),而后者面对的是人。系统问题再难,只要有足够的时间和资源,一般都可以解决。越难的问题,解决之后越有成就感。而人的问题,有时候看似很简单,却解决不了。是人,总要有头疼脑热,生病的时候。是人,免不了产生情绪,从而影响工作。有人的地方,就会有矛盾,就会有摩擦。简单地讲,系统会按照事先设定的逻辑运行,是死的,因此往往可控,可规划。而人是活的,不是输入几条命令就可以控制的,而是需要沟通,需要感情的。因此,大部分硅谷的工程师很“聪明”。他们主动选择“简单”地工作。白天好好地工作,晚上好好地生活。何必去“自寻烦恼”,转做管理呢。

    其实不光是硅谷的,其它地区的工程师都有一个共同的性格特点,追求简单,追求完美,思维方式上比较理性和逻辑性,看问题比较趋向于非黑即白。这样的性格非常适合做技术工作,可是我们中国的工程师有时候偏偏看不到自己的这个特点。

    不想当元帅的士兵不是好士兵。工程师希望向管理方向发展是非常正常的。但问题是为什么和怎样?我碰到过不只一个工程师告诉我,希望转做管理的原因是担心今后年级大了,技术能力跟不上了。我觉得非常可笑。这就好比是一个士兵说:我杀敌本领不行,不适合上战场,那就让我做军官吧。一个没做过士兵的元帅肯定不是好元帅。其实做技术和当兵毕竟不同,不是靠体力吃饭的。年级大点往往是优势。

    我觉得走技术路线对工程师性格的人是一条捷径。如果能静下心来仔细钻研技术,一定能在某个方面做得比别人好。这里的关键是好奇心和耐心。在今天这样的信息时代,找到答案并不是一件难事。难就难在有没有好奇心和耐心去找。比如,Java程序员天天都用到String这个类型。但有没有想过为什么 Java语言里有String和StringBuffer两种字符串类型,而不是一种?有没有去看过String和StringBuffer的源代码?再例如,天天做网站和HTTP打交道,有没有看过HTTP协议?有没有尝试过不用浏览器,wget等工具,而用最原始的telnet方式来访问网站?看看这 HTTP的头里到底是什么东东?在我面试过的工程师中,做过这几件事的人不到5%。

    一旦了解得比别人深,就容易看到问题本质,产生信心,激发乐趣。这时候你的解决方案就比别人漂亮,逐渐建立起了影响力,成为了“专家”。因此公司里的疑难杂症会主动找上门来。你就比别人得到了更多的解决问题的机会,从而更快地提升能力。一旦进入良性循环,你的进步就比别人快,但付出的却不一定比别人多。这时候你已经走上了捷径。

    在技术人才极度缺乏的中国,在众人盲目追求管理岗位的那点虚荣的今天,如果你的性格是工程师类型的,走技术路线其实是非常适合的。如果你才毕业,那你是最幸福的。你可以给自己制定3个甚至4个五年计划。例如5年打基础,10年变专家,15年国内知名,20年世界闻名。如果你已经奔三或者三十出头,那你快成熟了,但离开花结果还早呢。不信你看看下面几位我们都熟悉的人。

    拉里-沃尔(Larry Wall)33岁时出版了《Perl语言编程》一书。之前他是一个系统管理员。

    互联网之父温特-瑟夫(Vint Cerf)在发明TCP/IP时,已经35岁。

    万维网之父蒂姆·伯纳斯—李(Tim Berners-Lee)在37岁时才发明了万维网(WWW)。

    丹尼斯-里奇(Dennis Ritchie)的《C程序设计语言》一书出版时,他37岁。

    Java之父詹姆斯·戈士林(James Gosling)40岁时才因为发明Java而成名。

    苹果公司创始人之一史蒂夫?沃兹尼艾克(Steven Wozniak)在今年年初以首席科学家的身份加入一家创业公司,研发基于高速闪存技术的存储。他如今已经59岁了。

    本文来源:百度博客

    posted @ 2009-10-16 12:36 鸿雁 阅读(155) | 评论 (0)编辑 收藏

    SQL SERVER convert函数日期格式化应用(转)

    Sql Server 中一个非常强大的日期格式化函数
    Select CONVERT(varchar(100), GETDATE(), 0): 05 16 2006 10:57AM
    Select CONVERT(varchar(100), GETDATE(), 1): 05/16/06
    Select CONVERT(varchar(100), GETDATE(), 2): 06.05.16
    Select CONVERT(varchar(100), GETDATE(), 3): 16/05/06
    Select CONVERT(varchar(100), GETDATE(), 4): 16.05.06
    Select CONVERT(varchar(100), GETDATE(), 5): 16-05-06
    Select CONVERT(varchar(100), GETDATE(), 6): 16 05 06
    Select CONVERT(varchar(100), GETDATE(), 7): 05 16, 06
    Select CONVERT(varchar(100), GETDATE(), 8): 10:57:46
    Select CONVERT(varchar(100), GETDATE(), 9): 05 16 2006 10:57:46:827AM
    Select CONVERT(varchar(100), GETDATE(), 10): 05-16-06
    Select CONVERT(varchar(100), GETDATE(), 11): 06/05/16
    Select CONVERT(varchar(100), GETDATE(), 12): 060516
    Select CONVERT(varchar(100), GETDATE(), 13): 16 05 2006 10:57:46:937
    Select CONVERT(varchar(100), GETDATE(), 14): 10:57:46:967
    Select CONVERT(varchar(100), GETDATE(), 20): 2006-05-16 10:57:47
    Select CONVERT(varchar(100), GETDATE(), 21): 2006-05-16 10:57:47.157
    Select CONVERT(varchar(100), GETDATE(), 22): 05/16/06 10:57:47 AM
    Select CONVERT(varchar(100), GETDATE(), 23): 2006-05-16
    Select CONVERT(varchar(100), GETDATE(), 24): 10:57:47
    Select CONVERT(varchar(100), GETDATE(), 25): 2006-05-16 10:57:47.250
    Select CONVERT(varchar(100), GETDATE(), 100): 05 16 2006 10:57AM
    Select CONVERT(varchar(100), GETDATE(), 101): 05/16/2006
    Select CONVERT(varchar(100), GETDATE(), 102): 2006.05.16
    Select CONVERT(varchar(100), GETDATE(), 103): 16/05/2006
    Select CONVERT(varchar(100), GETDATE(), 104): 16.05.2006
    Select CONVERT(varchar(100), GETDATE(), 105): 16-05-2006
    Select CONVERT(varchar(100), GETDATE(), 106): 16 05 2006
    Select CONVERT(varchar(100), GETDATE(), 107): 05 16, 2006
    Select CONVERT(varchar(100), GETDATE(), 108): 10:57:49
    Select CONVERT(varchar(100), GETDATE(), 109): 05 16 2006 10:57:49:437AM
    Select CONVERT(varchar(100), GETDATE(), 110): 05-16-2006
    Select CONVERT(varchar(100), GETDATE(), 111): 2006/05/16
    Select CONVERT(varchar(100), GETDATE(), 112): 20060516
    Select CONVERT(varchar(100), GETDATE(), 113): 16 05 2006 10:57:49:513
    Select CONVERT(varchar(100), GETDATE(), 114): 10:57:49:547
    Select CONVERT(varchar(100), GETDATE(), 120): 2006-05-16 10:57:49
    Select CONVERT(varchar(100), GETDATE(), 121): 2006-05-16 10:57:49.700
    Select CONVERT(varchar(100), GETDATE(), 126): 2006-05-16T10:57:49.827
    Select CONVERT(varchar(100), GETDATE(), 130): 18 ???? ?????? 1427 10:57:49:907AM
    Select CONVERT(varchar(100), GETDATE(), 131): 18/04/1427 10:57:49:920AM

    posted @ 2009-09-23 15:14 鸿雁 阅读(174) | 评论 (0)编辑 收藏

    oracle定时任务详解(转自CSDN)

     

    DBMS_JOB系统包是Oracle“任务队列”子系统的API编程接口。DBMS_JOB包对于任务队列提供了下面这些功能:提交并且执行一个任务、改变任务的执行参数以及删除或者临时挂起任务等。

    DBMS_JOB包是由ORACLE_HOME目录下的rdbms/admin子目录下的DBMSJOB.SQL和PRVTJOB.PLB 这两个脚本文件创建的。这两个文件被CATPROC.SQL脚本文件调用,而CATPROC.SQL这个文件一般是在数据库创建后立即执行的。脚本为DBMS_JOB包创建了一个公共同义词,并给该包授予了公共的可执行权限,所以所有的Oracle用户均可以使用这个包。

    下面几个数据字典视图是关于任务队列信息的,主要有DBA_JOBS, USER_JOBS和DBA_JOBS_RUNNING。这些字典视图是由名为CATJOBQ.SQL的脚本文件创建的。该脚本文件和创建DBMS_JOB包的脚本文件一样在ORACLE_HOME目录的rdbms/admin子目录中,同样也是由脚本文件CATPROC.SQL调用。

    最后,要使任务队列能正常运行,还必须启动它自己专有的后台过程。启动后台过程是通过在初始化文件init*.ora(实例不同,初始化文件名也略有不同)中设置初始化参数来进行的。下面就是该参数:

    JOB_QUEUE_PROCESSES = n 

    其中,n可以是0到36之间的任何一个数。除了该参数以外,还有几个关于任务队列的初始化参数,本文后面将会对其进行详细讨论。

    DBMS_JOB包中包含有许多过程,见表1所示。

    表1 DBMS_JOB包

     

    名称 类型 描述
    DBMS_JOB.ISUBMIT 过程 提交一个新任务,用户指定一个任务号
    DBMS_JOB.SUBMIT 过程 提交一个新任务,系统指定一个任务号
    DBMS_JOB.REMOVE 过程 从队列中删除一个已经存在的任务
    DBMS_JOB.CHANGE 过程 更改用户设定的任务参数
    DBMS_JOB.WHAT 过程 更改PL/SQL任务定义
    DBMS_JOB.NEXT_DATE 过程 更改任务下一次运行时间
    DBMS_JOB.INTERVAL 过程 更改任务运行的时间间隔
    DBMS_JOB.BROKEN 过程 将任务挂起,不让其重复运行
    DBMS_JOB.RUN 过程 在当前会话中立即执行任务
    DBMS_JOB.USER_EXPORT 过程 创建文字字符串,用于重新创建一个任务

    三、DBMS_JOB包参数

    DBMS_JOB包中所有的过程都有一组相同的公共参数,用于定义任务,任务的运行时间以及任务定时运行的时间间隔。这些公共任务定义参数见表2所示。

    表2 DBMS_JOB过程的公共参数

     

    名称 类型 注释
    Job BINARY_INTEGER 任务的唯一识别号
    What VARCHAR2 作为任务执行的PL/SQL代码
    Next_date VARCHAR2 任务下一次运行的时间
    Interval VARCHAR2 日期表达式,用来计算下一次任务运行的时间

    下面我们来详细讨论这些参数的意义及用法。

    1、job

    参数job是一个整数,用来唯一地标示一个任务。该参数既可由用户指定也可由系统自动赋予,这完全取决于提交任务时选用了那一个任务提交过程。DBMS_JOB.SUBMIT过程通过获得序列SYS.JOBSEQ的下一个值来自动赋予一个任务号。该任务号是作为一个OUT参数返回的,所以调用者随后可以识别出提交的任务。而DBMS_JOB.ISUBMIT过程则由调用者给任务指定一个识别号,这时候,任务号的唯一性就完全取决于调用者了。

    除了删除或者重新提交任务,一般来说任务号是不能改变的。即使当数据库被导出或者被导入这样极端的情况,任务号也将被保留下来。所以在执行含有任务的数据的导入/导出操作时很可能会发生任务号冲突的现象。

    2、what

    what参数是一个可以转化为合法PL/SQL调用的字符串,该调用将被任务队列自动执行。在what参数中,如果使用文字字符串,则该字符串必须用单引号括起来。 what参数也可以使用包含我们所需要字符串值的VARCHAR2变量。实际的PL/SQL调用必须用分号隔开。在PL/SQL调用中如果要嵌入文字字符串,则必须使用两个单引号。

    what参数的长度在Oracle7.3中限制在2000个字节以内,在Oracle 8.0以后,扩大到了4000个字节,这对于一般的应用已完全足够。该参数的值一般情况下都是对一个PL/SQL存储过程的调用。在实际应用中,尽管可以使用大匿名Pl/SQL块,但建议大家最好不要这样使用。还有一个实际经验就是最好将存储过程调用封装在一个匿名块中,这样可以避免一些比较莫名错误的产生。我来举一个例子,一般情况下,what参数可以这样引用:

     

    what =>’my_procedure(parameter1);’

    但是比较安全的引用,应该这样写:

    what =>’begin my_procedure(parameter1); end;’

    任何时候,我们只要通过更改what参数就可以达到更改任务定义的目的。但是有一点需要注意,通过改变what参数来改变任务定义时,用户当前的会话设置也被记录下来并成为任务运行环境的一部分。如果当前会话设置和最初提交任务时的会话设置不同,就有可能改变任务的运行行为。意识到这个潜在的副作用是非常重要的,无论何时只要应用到任何DBMS_JOB过程中的what参数时就一定要确保会话设置的正确。

    3、next_date

    Next_date参数是用来调度任务队列中该任务下一次运行的时间。这个参数对于DBMS_JOB.SUBMIT和DBMS_JOB.BROKEN这两个过程确省为系统当前时间,也就是说任务将立即运行。

    当将一个任务的next_date参数赋值为null时,则该任务下一次运行的时间将被指定为4000年1月1日,也就是说该任务将永远不再运行。在大多数情况下,这可能是我们不愿意看到的情形。但是,换一个角度来考虑,如果想在任务队列中保留该任务而又不想让其运行,将next_date设置为null却是一个非常简单的办法。

    Next_date也可以设置为过去的一个时间。这里要注意,系统任务的执行顺序是根据它们下一次的执行时间来确定的,于是将next_date参数设置回去就可以达到将该任务排在任务队列前面的目的。这在任务队列进程不能跟上将要执行的任务并且一个特定的任务需要尽快执行时是非常有用的。

    4、Interval

    Internal参数是一个表示Oracle合法日期表达式的字符串。这个日期字符串的值在每次任务被执行时算出,算出的日期表达式有两种可能,要么是未来的一个时间要么就是null。这里要强调一点:很多开发者都没有意识到next_date是在一个任务开始时算出的,而不是在任务成功完成时算出的。

    当任务成功完成时,系统通过更新任务队列目录表将前面算出的next_date值置为下一次任务要运行的时间。当由interval表达式算出next_date是null时,任务自动从任务队列中移出,不会再继续执行。因此,如果传递一个null值给interval参数,则该任务仅仅执行一次。

    通过给interval参数赋各种不同的值,可以设计出复杂运行时间计划的任务。本文后面的“任务间隔和日期算法”将对interval表达式进行详细讨论,并给出一个实际有用interval表达式的例子。

    四、任务队列架构和运行环境

    任务队列在Oracle系统中其实是一个子系统,它具有自己特定的后台过程和目录表。该子系统设计的目的是为了能不在用户干预下自动运行PL/SQL过程。

    1、任务队列后台过程

    任务队列(SNP)后台过程随着Oracle实例的启动而同时启动。在文章前面已经谈到初始化文件init.ora中的参数JOB_QUEUE_PROCESSES,用来设置有几个队列过程。这里设置了几个过程,系统中就会有几个SNP过程被启动。JOB_QUEUE_PROCESSES这个参数,可以是0到36中的任何一个数,也就是说对于每个Oracle实例最多可以有36个SNP过程,也可以不支持队列过程(=0)。在大多数操作系统中,SNP三个字母常作为过程名的一部分出现。如,在unix系统中,如果该Oracle实例名为ora8,有三个任务队列过程,则这三个任务队列过程名称为:

     

    ora_ora8_snp0
    ora_ora8_snp1
    ora_ora8_snp2

    SNP后台过程和其他的Oracle后台过程的一个重要区别就是杀掉一个SNP过程不会影响到Oracle实例。当一个任务队列过程失控或者消耗太多的资源时,就可以将其杀掉,当然这种情况不是经常遇到的。当一个SNP过程被杀掉或者失败时,Oracle就自动启动一个新的SNP过程来代替它。

    2、有关任务队列的初始化参数

    初始化文件init.ora中的几个参数控制着任务队列后台的运行,下面我们将对其进行详细讨论。

    (1)、JOB_QUEUE_INTERVAL

    任务队列过程定期唤醒并检查任务队列目录表是否有任务需要执行。参数JOB_QUEUE_INTERVAL决定SNP过程两次检查目录表之间“休眠”多长时间(单位为秒)。间隔设的太小会造成由于SNP过程不断检查目录表而导致不必要的系统吞吐量。相反如果间隔设得太大,SNP过程在特定的时间没有被唤醒,那个时间的任务就不会能被运行。最佳的时间间隔设置要综合考虑系统环境中不同的任务,60秒的确省设置可以满足大多数的应用。

    (2)、JOB_QUEUE_KEEP_CONNECTIONS

    除了前面介绍的JOB_QUEUE_PROCESS和JOB_QUEUE_INTERVAL两个参数以外,影响SNP后台过程行为的第三个参数是JOB_QUEUE_KEEP_CONNECTIONS。当该参数为TRUE时,SNP过程在两个任务的运行期间(也就是休眠期间),仍然和Oracle保持开放的连接。相反,如果为FALSE时,SNP过程将和数据库断开连接,当唤醒时刻到来时又重新连接并检查任务队列。

    选择这两种方法中的那一种,主要是考虑任务队列的有效性和数据库关闭方法。长期保持连接的效率比较高,但任务队列会受到正常关闭数据库的影响。这是因为任务队列过程对于服务器管理器看来和一个普通用户的过程没有什么不同,而正常的关闭数据库需要让所有的用户都断开连接。而断开连接和重新连接又给数据库增加了负荷,但是可定期地使数据库没有可连接SNP过程,也就可以使数据库正常关闭。对于有很多任务或者是任务重复执行的时间间隔较短(一个小时或者更少)的环境,一般将JOB_QUEUE_KEEP_CONNECTIOONS设置为TRUE,并修改关闭数据库的脚本为立即关闭。对于严格要求采用正常方式关闭的数据库或者是任务较少,重复间隔较长的环境,一般将该参数设置为FALSE。最好,要提醒一句,SNP过程仅在没有任何任务运行时才断开,这种情况下,那些需要比较长时间运行的任务SNP将在它们的生命周期内一致保持开放的连接,这就延迟了正常关闭数据库的时间。

    3、建立运行环境

    当SNP过程唤醒时,它首先查看任务队列目录中所有的任务是否当前的时间超过了下一次运行的日期时间。SNP检测到需要该时间立即执行的任务后,这些任务按照下一次执行日期的顺序依次执行。当SNP过程开始执行一个任务时,其过程如下:

    1. 以任务所有者的用户名开始一个新的数据库会话。
    2. 当任务第一次提交或是最后一次被修改时,更改会话NLS设置和目前就绪的任务相匹配。
    3. 通过interval日期表达式和系统时间,计算下一次执行时间。
    4. 执行任务定义的PL/SQL
    5. 如果运行成功,任务的下一次执行日期(next_date)被更新,否则,失败计数加1。
    6. 经过JOB_QUEUS_INTERVAL秒后,又到了另一个任务的运行时间,重复上面的过程。

    在前两步中,SNP过程创建了一个模仿用户运行任务定义的PL/SQL的会话环境。然而,这个模仿的运行环境并不是和用户实际会话环境完全一样,需要注意以下两点:第一,在任务提交时任何可用的非确省角色都将在任务运行环境中不可用。因此,那些想从非确省角色中取得权限的任务不能提交,用户确省角色的修改可以通过在任务未来运行期间动态修改来完成。第二,任何任务定义本身或者过程执行中需要的数据库联接都必须完全满足远程的用户名和密码。SNP过程不能在没有显式指明口令的情况下初始化一个远程会话。显然,SNP过程不能假定将本地用户的口令作为远程运行环境会话设置的一部分。

    提交的任务如果运行失败会怎么样呢?当任务运行失败时,SNP过程在1分钟后将再次试图运行该任务。如果这次运行又失败了,下一次尝试将在2分钟后进行,再下一次在4分钟以后。任务队列每次加倍重试间隔直到它超过了正常的运行间隔。在连续16次失败后,任务就被标记为中断的(broken),如果没有用户干预,任务队列将不再重复执行。

    五、任务队列字典表和视图

    任务队列中的任务信息可以通过表3所示的几个字典视图来查看,这些视图是由CATJOBQ.sql脚本创建的。表4和5是各个视图每个字段的含义。

    表3. 任务队列中关于任务的数据字典视图

     

    视图名 描述
    DBA_JOBS 本数据库中定义到任务队列中的任务
    DBA_JOBS_RUNNING 目前正在运行的任务
    USER_JOBS 当前用户拥有的任务

    表4. DBA_JOBS 和 USER_JOBS.字典视图的字段含义

     

    字段(列) 类型 描述
    JOB NUMBER 任务的唯一标示号
    LOG_USER VARCHAR2(30) 提交任务的用户
    PRIV_USER VARCHAR2(30) 赋予任务权限的用户
    SCHEMA_USER VARCHAR2(30) 对任务作语法分析的用户模式
    LAST_DATE DATE 最后一次成功运行任务的时间
    LAST_SEC VARCHAR2(8) 如HH24:MM:SS格式的last_date日期的小时,分钟和秒
    THIS_DATE DATE 正在运行任务的开始时间,如果没有运行任务则为null
    THIS_SEC VARCHAR2(8) 如HH24:MM:SS格式的this_date日期的小时,分钟和秒
    NEXT_DATE DATE 下一次定时运行任务的时间
    NEXT_SEC VARCHAR2(8) 如HH24:MM:SS格式的next_date日期的小时,分钟和秒
    TOTAL_TIME NUMBER 该任务运行所需要的总时间,单位为秒
    BROKEN VARCHAR2(1) 标志参数,Y标示任务中断,以后不会运行
    INTERVAL VARCHAR2(200) 用于计算下一运行时间的表达式
    FAILURES NUMBER 任务运行连续没有成功的次数
    WHAT VARCHAR2(2000) 执行任务的PL/SQL块
    CURRENT_SESSION_LABEL RAW MLSLABEL 该任务的信任Oracle会话符
    CLEARANCE_HI RAW MLSLABEL 该任务可信任的Oracle最大间隙
    CLEARANCE_LO RAW MLSLABEL 该任务可信任的Oracle最小间隙
    NLS_ENV VARCHAR2(2000) 任务运行的NLS会话设置
    MISC_ENV RAW(32) 任务运行的其他一些会话参数

    表 5. 视图DBA_JOBS_RUNNING的字段含义

     

    数据类型 描述
    SID NUMBER 目前正在运行任务的会话ID
    JOB NUMBER 任务的唯一标示符
    FAILURES NUMBER 连续不成功执行的累计次数
    LAST_DATE DATE 最后一次成功执行的日期
    LAST_SEC VARCHAR2(8) 如HH24:MM:SS格式的last_date日期的小时,分钟和秒
    THIS_DATE DATE 目前正在运行任务的开始日期
    THIS_SEC VARCHAR2(8) 如HH24:MM:SS格式的this_date日期的小时,分钟和秒

    六、任务重复运行间隔和间隔设计算法

    任务重复运行的时间间隔取决于interval参数中设置的日期表达式。下面就来详细谈谈该如何设置interval参数才能准确满足我们的任务需求。一般来讲,对于一个任务的定时执行,有三种定时要求。

    1. 在一个特定的时间间隔后,重复运行该任务。
    2. 在特定的日期和时间运行任务。
    3. 任务成功完成后,下一次执行应该在一个特定的时间间隔之后。

    第一种调度任务需求的日期算法比较简单,即'SYSDATE+n',这里n是一个以天为单位的时间间隔。表6给出了一些这种时间间隔设置的例子。

    表6 一些简单的interval参数设置例子

     

    描述 Interval参数值
    每天运行一次 'SYSDATE + 1'
    每小时运行一次 'SYSDATE + 1/24'
    每10分钟运行一次 'SYSDATE + 10/(60*24)'
    每30秒运行一次 'SYSDATE + 30/(60*24*60)'
    每隔一星期运行一次 'SYSDATE + 7'
    不再运行该任务并删除它 NULL

    表6所示的任务间隔表达式不能保证任务的下一次运行时间在一个特定的日期或者时间,仅仅能够指定一个任务两次运行之间的时间间隔。例如,如果一个任务第一次运行是在凌晨12点,interval指定为'SYSDATE + 1',则该任务将被计划在第二天的凌晨12点执行。但是,如果某用户在下午4点手工(DBMS_JOB.RUN)执行了该任务,那么该任务将被重新定时到第二天的下午4点。还有一个可能的原因是如果数据库关闭或者说任务队列非常的忙以至于任务不能在计划的那个时间点准时执行。在这种情况下,任务将试图尽快运行,也就是说只要数据库一打开或者是任务队列不忙就开始执行,但是这时,运行时间已经从原来的提交时间漂移到了后来真正的运行时间。这种下一次运行时间的不断“漂移”是采用简单时间间隔表达式的典型特征。

    第二种调度任务需求相对于第一种就需要更复杂的时间间隔(interval)表达式,表7是一些要求在特定的时间运行任务的interval设置例子。

    表 7. 定时到特定日期或时间的任务例子

     

    描述 INTERVAL参数值
    每天午夜12点 'TRUNC(SYSDATE + 1)'
    每天早上8点30分 'TRUNC(SYSDATE + 1) + (8*60+30)/(24*60)'
    每星期二中午12点 'NEXT_DAY(TRUNC(SYSDATE ), ''TUESDAY'' ) + 12/24'
    每个月第一天的午夜12点 'TRUNC(LAST_DAY(SYSDATE ) + 1)'
    每个季度最后一天的晚上11点 'TRUNC(ADD_MONTHS(SYSDATE + 2/24, 3 ), 'Q' ) -1/24'
    每星期六和日早上6点10分 'TRUNC(LEAST(NEXT_DAY(SYSDATE, ''SATURDAY"), NEXT_DAY(SYSDATE, "SUNDAY"))) + (6×60+10)/(24×60)'

    第三种调度任务需求无论通过怎样设置interval日期表达式也不能满足要求。这时因为一个任务的下一次运行时间在任务开始时才计算,而在此时是不知道任务在何时结束的。遇到这种情况怎么办呢?当然办法肯定是有的,我们可以通过为任务队列写过程的办法来实现。这里我只是简单介绍以下,可以在前一个任务队列执行的过程中,取得任务完成的系统时间,然后加上指定的时间间隔,拿这个时间来控制下一个要执行的任务。这里有一个前提条件,就是目前运行的任务本身必须要严格遵守自己的时间计划。

    结论

    Oracle中的定时任务是在Oracle系统中是一个非常重要的子系统,运用得当,可以极大的提高我们的系统运行和维护能力。而Oracle数据复制的延迟事务队列管理完全是基于Oracle的队列任务,对其的深刻理解有助于我们更好地管理数据复制。


    posted @ 2009-08-26 10:30 鸿雁 阅读(437) | 评论 (0)编辑 收藏

    IText使用PDF模板输出报表的实践(转)

    本文所要用到的工具或jar主要有: Acrobat 8 这个主要用来制作PDF模板、eclipse这个看你喜欢咯(你用其他也行) 、 itext.jar、
    还有为了解决中文的输出问题,需要多下载一个名为iTextAsian.jar的JAR包。这个包里面定义了与中文输出相关的一些文件。
    好了,需要做的就是这些了,简单的PDF生成这里就不再作介绍了,本文主要讲解如何使用PDF模板。
    我们先来看看制作出来的效果:


    上图表格上及表格中的数据是动态添加进去的,页数为两页(为节约版面现只显示一页)
    两页都是用的同一模板的,
    1、 模板的制作:
    我主要使用的是Acrobat8.0,上面所用到的模板是由 周工作报告 模板修改而来的,如果想学习如何新建一个新的模板,大家可以参照下这里吧!
    http://lxy19791111.javaeye.com/blog/102848
    2、 取得每个表单域的名字
    模板制作好后,要插入数据首先就要知道需要插在模板中位置,
    Java代码 复制代码
    1.   //需要生成后的PDF    
    2. FileOutputStream fos = new FileOutputStream("c:/test/Pdf.pdf");    
    3.   //PDF模板路径    
    4. String TemplatePDF ="c:/test/PdfTemplate.pdf";    
    5.        PdfReader reader = new PdfReader(TemplatePDF);      
    6. PdfStamper stamp = new PdfStamper(reader,fos);    
    7. AcroFields form = stamp.getAcroFields();    
    8. for (Iterator it = form.getFields().keySet().iterator(); it    
    9. .hasNext();) {    
    10. System.out.println(it.next());    
    11. }   

    这个是打印后的部分结果:

    我们只取后面那个命名就行,如"星期四[3]"
    当然,模板是你自己定义,文本域的命名你当然知道了,这里只是作个简单介绍而已。
    3、下面是插入数据及PDF合并的代码:
    Java代码 复制代码
    1. package com.golden.info.test;   
    2.   
    3. import java.io.ByteArrayOutputStream;   
    4. import java.io.FileNotFoundException;   
    5. import java.io.FileOutputStream;   
    6. import java.io.IOException;   
    7. import java.util.Date;   
    8.   
    9. import com.lowagie.text.Document;   
    10. import com.lowagie.text.DocumentException;   
    11. import com.lowagie.text.pdf.AcroFields;   
    12. import com.lowagie.text.pdf.PdfCopy;   
    13. import com.lowagie.text.pdf.PdfImportedPage;   
    14. import com.lowagie.text.pdf.PdfReader;   
    15. import com.lowagie.text.pdf.PdfStamper;   
    16. public class TestPdfTemplate {     
    17.     public static void main(String[] args) {   
    18.         try {   
    19.             int count = 8;// 总记录数   
    20.             int pageCount = 4;// 每页记录数   
    21.             int index = 1// 表格序号   
    22.             int page = 0;// 总共页数   
    23.             /** 主要控制总共的页数*/  
    24.             if (count >= pageCount && count % pageCount == 0) {   
    25.                 page = count / pageCount;   
    26.             } else {   
    27.                 page = count / pageCount + 1;   
    28.             }   
    29.             String TemplatePDF = "c:/test/PdfTemplate.pdf";//设置模板路径   
    30.             FileOutputStream fos = new FileOutputStream("c:/test/Pdf.pdf");//需要生成PDF   
    31.                
    32.             ByteArrayOutputStream baos[] = new ByteArrayOutputStream[page];//用于存储每页生成PDF流   
    33.             /** 向PDF模板中插入数据 */  
    34.             for (int item = 0; item < page; item++) {   
    35.                 baos[item] = new ByteArrayOutputStream();   
    36.                 PdfReader reader = new PdfReader(TemplatePDF);   
    37.                 PdfStamper stamp = new PdfStamper(reader, baos[item]);   
    38.                 AcroFields form = stamp.getAcroFields();   
    39.                 form.setField("DepartmnetNmae""蓝飞");//插入的数据都为字符类型   
    40.                 form.setField("qq""252462807");                  
    41.                 form.setField("pageNumber""第" + (item + 1) + "页,共" + page   
    42.                         + "页");   
    43.                 if (count % pageCount != 0 && item == page - 1) {   
    44.                     System.out.println("====pageCount+" + pageCount + "=====");   
    45.                     pageCount = count % pageCount;   
    46.                 }   
    47.                 /**因为PDF中的表格其实是众多的文本域组成,就是一个数组,所以把它循环出来就可以了*/  
    48.                 for (int j = 0; j < pageCount; j++) {   
    49.                     form.setField("ProjectTask[" + j + "]", index + "");   
    50.                     form.setField("星期一[" + j + "]""星期一[" + index + "]");   
    51.                     form.setField("星期二[" + j + "]""星期二[" + index + "]");   
    52.                     form.setField("星期三[" + j + "]""星期三[" + index + "]");   
    53.                     form.setField("星期四[" + j + "]""星期四[" + index + "]");   
    54.                     form.setField("星期五[" + j + "]""星期五[" + index + "]");   
    55.                     form.setField("星期六[" + j + "]""星期六[" + index + "]");   
    56.                     form.setField("星期日[" + j + "]""星期日[" + index + "]");   
    57.                     form.setField("意见[" + j + "]""同意[" + j + "]");   
    58.                     index++;   
    59.                 }   
    60.                 stamp.setFormFlattening(true); // 千万不漏了这句啊, */   
    61.                 stamp.close();   
    62.             }   
    63.             Document doc = new Document();   
    64.             PdfCopy pdfCopy = new PdfCopy(doc, fos);   
    65.             doc.open();   
    66.             PdfImportedPage impPage = null;   
    67.             /**取出之前保存的每页内容*/  
    68.             for (int i = 0; i < page; i++) {   
    69.                 impPage = pdfCopy.getImportedPage(new PdfReader(baos[i]   
    70.                         .toByteArray()), 1);   
    71.                 pdfCopy.addPage(impPage);   
    72.             }   
    73.             doc.close();//当文件拷贝  记得关闭doc   
    74.         } catch (FileNotFoundException e) {   
    75.             e.printStackTrace();   
    76.         } catch (IOException e) {   
    77.             e.printStackTrace();   
    78.         } catch (DocumentException e) {   
    79.             e.printStackTrace();   
    80.         }   
    81.   
    82.     }   
    83. }  


    至于,生于PDF后,想打印出来,只要调用以下代码就行了
    Java代码 复制代码
    1.            
    2. try{                
    3. Executable ex = new Executable();          
    4.        ex.openDocument("c:/test/Pdf.pdf");              
    5.     ex.printDocument("c:/test/Pdf.pdf");   
    6.     }catch(IOException e){       
    7.     e.printStackTrace();         
    8.     }  

    到这里,运用上面的那些代码,就完成了PDF模板输出报表.
    (PDF模板、代码跟运行结果在附件里)
    有错误之处请指正.
    也希望这篇文章可以帮到您.

    posted @ 2009-08-12 09:53 鸿雁 阅读(4501) | 评论 (0)编辑 收藏

    利用 iText 实现 PDF 报表下载 (转)

         摘要: 有个朋友的项目需要用到 PDF 报表下载,之前我只做过 Excel 的,相信再做一次 PDF 的下载一定很有趣吧。在网上找了一大圈,似乎 iText 比较符合我的要求,而且这个工具很早很早以前就有了,生命力很旺盛。进入 iText 的主页(http://www.lowagie.com/iText/),发现作者很勤劳,最近2个月都有新版本发布。哪知道现在高兴得太早了,一堆问题接踵而至。 下载倒...  阅读全文

    posted @ 2009-08-12 09:50 鸿雁 阅读(415) | 评论 (0)编辑 收藏

    iText JSP中生成PDF(入门)

    iText简介

      iText是一个开放源码的Java类库,可以用来方便地生成PDF文件。大家通过访问http://sourceforge.net/project/showfiles.php?group_id=15255&release_id=167948下载最新版本的类库,下载完成之后会得到一个.jar包,把这个包加入JDK的classpath即可使用。如果生成的PDF文件中需要出现中文、日文、韩文字符,则还需要通过访问http://itext.sourceforge.net/downloads/iTextAsian.jar下载iTextAsian.jar包。

      关于iText类库的使用,http://www.lowagie.com/iText/tutorial/index.html有比较详细的教程。该教程从入门开始,比较系统地介绍了在PDF文件中放入文字、图片、表格等的方法和技巧。读完这片教程,大致就可以做一些从简单到复杂的PDF文件了。不过,试图通过教程解决在生成PDF文件过程中遇到的所有困难无疑是一种奢望。所以,阅读iText的api文档显得非常重要。读者在下载类库的同时,也可以下载类库的文档。

    可参考资料 :  

    http://dev.csdn.net/article/62/62119.shtm       http://myowndream.blog.com.cn/archives/2007/2089386.shtml
    http://tag.csdn.net/tag/itext.xml

    一  HelloWorld实例

    以下是上述教程中一个最简单的例子,这个例子刻画了通过iText生成PDF文件的一般程序框架。读者只需要在document.open();和document.close();两条语句中间加入自己希望放在PDF文件中的内容即可。该例子只在PDF文件中加了“Hello World“一行文字。

    Document document = new Document();

    try{
        PdfWriter.getInstance(document, new FileOutputStream ("Chap0101.pdf"));

        document.open();

        document.add(new Paragraph("Hello World"));
    }catch(DocumentException de){
         System.err.println(de.getMessage());
    }catch(IOException ioe){
         System.err.println(ioe.getMessage());
    }
     
    document.close();

    可以看到一个PDF文件的输出,总共只需要5个步骤
    a.创建一个Document实例
      Document document = new Document();
    b.将Document实例和文件输出流用PdfWriter类绑定在一起
      PdfWriter.getInstance(document,new FileOutputStream("HelloWorld.pdf"));
    c.打开文档
      document.open();
    d.在文档中添加文字
      document.add(new Paragraph("Hello World"));
    e.关闭文档
      document.close();
    这样5个步骤,就可以生成一个PDF文档了。

    然而在PDF中指定文字、图画、表格的位置是一件非常麻烦的事情。除了不断地在程序中修改位置、然后运行程序、生成PDF文件、观察元素在PDF中的位置是否合理这样的过程以外,似乎还没有其它更好的方法。

    二。如何使用jsp、servlet输出iText生成的pdf?
       如果每次都在服务端生成一个PDF文件给用户,不仅麻烦,而且浪费服务器资源,最好的方法就是以二进制流的形式输送到客户端。
    1)JSP输出:

    <%@ page import="java.io.*,java.awt.Color,com.lowagie.text.*,com.lowagie.text.pdf.*"%>
    <%
     response.setContentType( "application/pdf" );
     Document document = new Document();
     ByteArrayOutputStream buffer = new ByteArrayOutputStream();
     PdfWriter writer=PdfWriter.getInstance( document, buffer );

     document.open();

     document.add(new Paragraph("Hello World"));

     document.close();

     DataOutput output = new DataOutputStream( response.getOutputStream() );
     byte[] bytes = buffer.toByteArray();
     response.setContentLength(bytes.length);
     for( int i = 0; i < bytes.length; i++ ){
      output.writeByte( bytes[i] );
     }

    %>

    2)servlet输出,稍微改造下就可以使用在struts中:

    import java.io.*;
    import javax.servlet.*;
    import javax.servlet.http.*;
    import com.lowagie.text.*;
    import com.lowagie.text.pdf.*;

    public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException,ServletException{

      Document document = new Document(PageSize.A4, 36,36,36,36);
      ByteArrayOutputStream ba = new ByteArrayOutputStream();

      try{
       PdfWriter writer = PdfWriter.getInstance(document, ba);

       document.open();

       document.add(new Paragraph("Hello World"));
      }catch(DocumentException de){
       de.printStackTrace();
       System.err.println("A Document error:" +de.getMessage());
        }

      document.close();

      response.setContentType("application/pdf");
      response.setContentLength(ba.size());
      ServletOutputStream out = response.getOutputStream();
      ba.writeTo(out);
      out.flush();
    }

    三。如何输出中文?
         首先需要下载iTextAsian.jar包,可以到iText的主站上下,ireport也是需要这个包的。然后定义中文字体:

        private static final Font getChineseFont() {
             Font FontChinese
    = null;
            
    try {
                 BaseFont bfChinese
    = BaseFont.createFont("STSong-Light",
                        
    "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
                 FontChinese
    = new Font(bfChinese, 12, Font.NORMAL);
             }
    catch (DocumentException de) {
                 System.err.println(de.getMessage());
             }
    catch (IOException ioe) {
                 System.err.println(ioe.getMessage());
             }
            
    return FontChinese;
         }


    我将产生中文字体封装在方法内,自定义一个段落PDFParagraph继承自Paragraph,默认使用中文字体:

    class PDFParagraph extends Paragraph {
            
    public PDFParagraph(String content) {
                
    super(content, getChineseFont());
             }
         }


    使用的时候就可以简化了:

    Paragraph par = new PDFParagraph("你好");


    四。如何设置PDF横向显示和打印?

    Rectangle rectPageSize = new Rectangle(PageSize.A4);// 定义A4页面大小
    rectPageSize = rectPageSize.rotate();// 加上这句可以实现A4页面的横置
    Document doc = new Document(rectPageSize,50,50,50,50);//4个参数,设置了页面的4个边距


    五。如何设置跨行和跨列?

    使用PdfPTable和PdfPCell 是没办法实现跨行的,只能跨列,要跨行使用com.lowagie.text.Table和com.lowagie.text.Cell类,Cell类有两个方法:setRowspan()和setColspan()。

    六。如何设置单元格边界宽度?

    Cell类的系列setBorderWidthXXXX()方法,比如setBorderWidthTop(),setBorderWidthRight()等

    七。如何设置表头?
    希望每一页都有表头,可以通过设置表头来实现。对于PdfPTable类来说,可以这样设置:

    PdfPTable table = new PdfPTable(3);
    table.setHeaderRows(
    2); // 设置了头两行为表格头


    而对于om.lowagie.text.Table类,需要在添加完所有表头的单元格后加上一句代码:

    table.endHeaders();


    八。如何设置列宽?

    Table table = new Table(8);
    float[] widths = { 0.10f, 0.15f, 0.21f, 0.22f, 0.08f, 0.08f, 0.10f,
                        
    0.06f };
    table.setWidths(widths);


    上面的代码设置了一个有8列的表格,通过一个float数组设置列宽,这里是百分比。

    九。单元格内段落文字居中和换行?
    居中通过Cell类来设置,一开始我以为设置段落对齐就可以了,没想到是需要设置单元格:

    cell.setHorizontalAlignment(Element.ALIGN_CENTER);



    转义符\n实现。在我的这个应用中,因为数据库取出的数据是为了显示在html上的,所以有很多<br>标签,可以使用正则表达式替换成"\n"

    "<br>1.测试<br>2.测试2".replaceAll("<br>|</br>","\n");


    十。如何显示页码?
    复杂的页码显示和水印添加,需要使用到PdfPageEventHelper、PdfTemplate等辅助类,具体的例子参见iText的文档,如果只是为了简单的显示页数,可以使用下面的代码:

                 HeaderFooter footer = new HeaderFooter(new Phrase("页码:",getChineseFont()), true);
                 footer.setBorder(Rectangle.NO_BORDER);
                 document.setFooter(footer);
                 document.open();

    你可能注意到了,添加footer需要在document.open之前。

    posted @ 2009-08-12 09:48 鸿雁 阅读(2322) | 评论 (0)编辑 收藏

    开发相关的重点技术介绍-转贴

         摘要: 数据库连接池的原理机制 数据库连接池的原理机制 ...  阅读全文

    posted @ 2009-05-15 10:32 鸿雁 阅读(1354) | 评论 (0)编辑 收藏

    Oracle 跨库 查询 复制表数据

    方法一:

    在目前绝大部分数据库有分布式查询的需要。下面简单的介绍如何在oracle中配置实现跨库访问。

    比如现在有2个数据库服务器,安装了2个数据库。数据库server A和B。现在来实现在A库中访问B的数据库。

    第一步、配置A服务器端的tnsnames.ora文件(TNSNAMES.ORA Network Configuration File),该文件存放的位置为:

    $ORACLE_HOME/network/admin/tnsnames.ora

    需要在该文件中增加对B库的配置项,格式如下

    ZBCDB3 =
    (DESCRIPTION =
        (ADDRESS_LIST =
          (ADDRESS = (PROTOCOL = TCP)(HOST = 10.1.50.6)(PORT = 1523))
        )
        (CONNECT_DATA =
          (SERVICE_NAME = zbcdb3)
        )
    )

    若在A库的另外一台客户端来访问B的数据库的话,同时也应该修改客户端的相应的文件。

    第二步、在A服务器的一个库中建立B的一个数据的DBLINK。语法如下:

    create database link dcmdb connect to dcmdb identified by dcmoptr using 'zbcdb3' ;

    然后可以实现分布式查询:

    select * from tabname@dcmdb where 1=1;

    方法二:

    首先创建数据库链接:
    CREATE PUBLIC DATABASE LINK 数据链名称 CONNECT TO 登陆用户名 IDENTIFIED BY 密码 USING '(DESCRIPTION =
         (ADDRESS_LIST =
           (ADDRESS = (PROTOCOL = TCP)(HOST = 对方Oracle服务器的IP地址)(PORT = 端口号))
         )
         (CONNECT_DATA =
    (SERVICE_NAME = 对方Oracle服务器服务名)
         )
       )'

    其中 数据链名称 为添加到本地Oracle数据库控制台(Oracle Enterprise Manager Console)树节点的服务名

    要查询对方数据库的表TableName语句如下:

    SELECT 字段名 FROM TableName@数据链名称;

    复制表数据:

    insert into 表名(字段名) (SELECT 字段名 FROM TableName@数据链名称);

    posted @ 2008-12-29 17:32 鸿雁 阅读(540) | 评论 (0)编辑 收藏

    SQL Server日期计算

    通常,你需要获得当前日期和计算一些其他的日期,例如,你的程序可能需要判断一个月的第一天或者最后一天。你们大部分人大概都知道怎样把日期进行分割(年、月、日等),然后仅仅用分割出来的年、月、日等放在几个函数中计算出自己所需要的日期!在这篇文章里,我将告诉你如何使用DATEADD和DATEDIFF函数来计算出在你的程序中可能你要用到的一些不同日期。 
           在使用本文中的例子之前,你必须注意以下的问题。大部分可能不是所有例子在不同的机器上执行的结果可能不一样,这完全由哪一天是一个星期的第一天这个设置决定。第一天(DATEFIRST)设定决定了你的系统使用哪一天作为一周的第一天。所有以下的例子都是以星期天作为一周的第一天来建立,也就是第一天设置为7。假如你的第一天设置不一样,你可能需要调整这些例子,使它和不同的第一天设置相符合。你可以通过@@DATEFIRST函数来检查第一天设置。 
     
           为了理解这些例子,我们先复习一下DATEDIFF和DATEADD函数。DATEDIFF函数计算两个日期之间的小时、天、周、月、年等时间间隔总数。DATEADD函数计算一个日期通过给时间间隔加减来获得一个新的日期。要了解更多的DATEDIFF和DATEADD函数以及时间间隔可以阅读微软联机帮助。 
     
           使用DATEDIFF和DATEADD函数来计算日期,和本来从当前日期转换到你需要的日期的考虑方法有点不同。你必须从时间间隔这个方面来考虑。比如,从当前日期到你要得到的日期之间有多少时间间隔,或者,从今天到某一天(比如1900-1-1)之间有多少时间间隔,等等。理解怎样着眼于时间间隔有助于你轻松的理解我的不同的日期计算例子。 
     
    一个月的第一天 
     
           第一个例子,我将告诉你如何从当前日期去这个月的最后一天。请注意:这个例子以及这篇文章中的其他例子都将只使用DATEDIFF和DATEADD函数来计算我们想要的日期。每一个例子都将通过计算但前的时间间隔,然后进行加减来得到想要计算的日期。 
     
           这是计算一个月第一天的SQL  脚本: 
           SELECT  DATEADD(mm,  DATEDIFF(mm,0,getdate()),  0) 
     
           我们把这个语句分开来看看它是如何工作的。最核心的函数是getdate(),大部分人都知道这个是返回当前的日期和时间的函数。下一个执行的函数DATEDIFF(mm,0,getdate())是计算当前日期和“1900-01-01  00:00:00.000”这个日期之间的月数。记住:时期和时间变量和毫秒一样是从“1900-01-01  00:00:00.000”开始计算的。这就是为什么你可以在DATEDIFF函数中指定第一个时间表达式为“0”。下一个函数是DATEADD,增加当前日期到“1900-01-01”的月数。通过增加预定义的日期“1900-01-01”和当前日期的月数,我们可以获得这个月的第一天。另外,计算出来的日期的时间部分将会是“00:00:00.000”。 
     
           这个计算的技巧是先计算当前日期到“1900-01-01”的时间间隔数,然后把它加到“1900-01-01”上来获得特殊的日期,这个技巧可以用来计算很多不同的日期。下一个例子也是用这个技巧从当前日期来产生不同的日期。 
     
      
    本周的星期一 
     
           这里我是用周(wk)的时间间隔来计算哪一天是本周的星期一。 
     
           SELECT  DATEADD(wk,  DATEDIFF(wk,0,getdate()),  0) 
     
    一年的第一天 
     
           现在用年(yy)的时间间隔来显示这一年的第一天。 
     
           SELECT  DATEADD(yy,  DATEDIFF(yy,0,getdate()),  0) 
     
    季度的第一天 
     
           假如你要计算这个季度的第一天,这个例子告诉你该如何做。 
     
           SELECT  DATEADD(qq,  DATEDIFF(qq,0,getdate()),  0) 
     
    当天的半夜 
     
           曾经需要通过getdate()函数为了返回时间值截掉时间部分,就会考虑到当前日期是不是在半夜。假如这样,这个例子使用DATEDIFF和DATEADD函数来获得半夜的时间点。 
     
           SELECT  DATEADD(dd,  DATEDIFF(dd,0,getdate()),  0) 
     
    深入DATEDIFF和DATEADD函数计算 
     
           你可以明白,通过使用简单的DATEDIFF和DATEADD函数计算,你可以发现很多不同的可能有意义的日期。 
     
           目前为止的所有例子只是仅仅计算当前的时间和“1900-01-01”之间的时间间隔数量,然后把它加到“1900-01-01”的时间间隔上来计算出日期。假定你修改时间间隔的数量,或者使用不同的时间间隔来调用DATEADD函数,或者减去时间间隔而不是增加,那么通过这些小的调整你可以发现和多不同的日期。 
     
           这里有四个例子使用另外一个DATEADD函数来计算最后一天来分别替换DATEADD函数前后两个时间间隔。 
     
    上个月的最后一天 
     
           这是一个计算上个月最后一天的例子。它通过从一个月的最后一天这个例子上减去3毫秒来获得。有一点要记住,在Sql  Server中时间是精确到3毫秒。这就是为什么我需要减去3毫秒来获得我要的日期和时间。 
     
           SELECT  dateadd(ms,-3,DATEADD(mm,  DATEDIFF(mm,0,getdate()),  0)) 
     
           计算出来的日期的时间部分包含了一个Sql  Server可以记录的一天的最后时刻(“23:59:59:997”)的时间。 
     
    去年的最后一天 
     
           连接上面的例子,为了要得到去年的最后一天,你需要在今年的第一天上减去3毫秒。 
     
           SELECT  dateadd(ms,-3,DATEADD(yy,  DATEDIFF(yy,0,getdate()),  0)) 
     
    本月的最后一天 
     
           现在,为了获得本月的最后一天,我需要稍微修改一下获得上个月的最后一天的语句。修改需要给用DATEDIFF比较当前日期和“1900-01-01”返回的时间间隔上加1。通过加1个月,我计算出下个月的第一天,然后减去3毫秒,这样就计算出了这个月的最后一天。这是计算本月最后一天的SQL脚本。 
     
           SELECT  dateadd(ms,-3,DATEADD(mm,  DATEDIFF(m,0,getdate())+1,  0)) 
     
    本年的最后一天 
     
           你现在应该掌握这个的做法,这是计算本年最后一天脚本 
     
           SELECT  dateadd(ms,-3,DATEADD(yy,  DATEDIFF(yy,0,getdate())+1,  0))。 
     
    本月的第一个星期一 
     
           好了,现在是最后一个例子。这里我要计算这个月的第一个星期一。这是计算的脚本。 
     
             select  DATEADD(wk,  DATEDIFF(wk,0,                                                         
                                   dateadd(dd,6-datepart(day,getdate()),getdate())       
                                                                                                     ),  0)                         
     
           在这个例子里,我使用了“本周的星期一”的脚本,并作了一点点修改。修改的部分是把原来脚本中“getdate()”部分替换成计算本月的第6天,在计算中用本月的第6天来替换当前日期使得计算可以获得这个月的第一个星期一。 
     
    总结 
     
           我希望这些例子可以在你用DATEADD和DATEDIFF函数计算日期时给你一点启发。通过使用这个计算日期的时间间隔的数学方法,我发现为了显示两个日期之间间隔的有用历法是有价值的。注意,这只是计算出这些日期的一种方法。要牢记,还有很多方法可以得到相同的计算结果。假如你有其他的方法,那很不错,要是你没有,我希望这些例子可以给你一些启发,当你要用DATEADD和DATEDIFF函数计算你程序可能要用到的日期时。 
     
    --------------------------------------------------------------- 
    附录,其他日期处理方法 
     
    1)去掉时分秒 
    declare  @  datetime 
    set  @  =  getdate()  --'2003-7-1  10:00:00' 
    SELECT  @,DATEADD(day,  DATEDIFF(day,0,@),  0) 
     
    2)显示星期几 
    select  datename(weekday,getdate())   
     
    3)如何取得某个月的天数 
    declare  @m  int 
    set  @m=2  --月份 
    select    datediff(day,'2003-'+cast(@m  as  varchar)+'-15'  ,'2003-'+cast(@m+1    as  varchar)+'-15') 
    另外,取得本月天数 
    select    datediff(day,cast(month(GetDate())  as  varchar)+'-'+cast(month(GetDate())  as  varchar)+'-15'  ,cast(month(GetDate())  as  varchar)+'-'+cast(month(GetDate())+1    as  varchar)+'-15') 
    或者使用计算本月的最后一天的脚本,然后用DAY函数区最后一天 
    SELECT  Day(dateadd(ms,-3,DATEADD(mm,  DATEDIFF(m,0,getdate())+1,  0))) 
     
    4)判断是否闰年: 
    SELECT  case  day(dateadd(mm,  2,  dateadd(ms,-3,DATEADD(yy,  DATEDIFF(yy,0,getdate()),  0))))  when  28  then  '平年'  else  '闰年'  end 
    或者 
    select  case  datediff(day,datename(year,getdate())+'-02-01',dateadd(mm,1,datename(year,getdate())+'-02-01')) 
    when  28  then  '平年'  else  '闰年'  end 
     
    5)一个季度多少天 
    declare  @m  tinyint,@time  smalldatetime 
    select  @m=month(getdate()) 
    select  @m=case  when  @m  between  1  and  3  then  1 
                           when  @m  between  4  and  6  then  4 
                           when  @m  between  7  and  9  then  7 
                           else  10  end 
    select  @time=datename(year,getdate())+'-'+convert(varchar(10),@m)+'-01' 
    select  datediff(day,@time,dateadd(mm,3,@time)) 


    Oracle与DB2获取当前系统时间的方法:

    Oracle中系统时间比较熟悉 是select sysdate from dual;

    db2中则是 SELECT CURRENT TIMESTAMP FROM SYSIBM.SYSDUMMY1
    还有
    select CURRENT DATE from SYSIBM.SYSDUMMY1;获得日期
    select CURRENT TIME from SYSIBM.SYSDUMMY1;获得时间

    posted @ 2008-11-25 10:43 鸿雁 阅读(236) | 评论 (0)编辑 收藏

    Oracle 游标使用大全

    查询  

        SELECT语句用于从数据库中查询数据,当在PL/SQL中使用SELECT语句时,要与INTO子句一起使用,查询的返回值被赋予INTO子句中的变量,变量的声明是在DELCARE中。SELECT             INTO语法如下: 
         SELECT [DISTICT|ALL]{*|column[,column,...]} 
         INTO (variable[,variable,...] |record) 
         FROM {table|(sub-query)}[alias] 
         WHERE............ 
        PL/SQL中SELECT语句只返回一行数据。如果超过一行数据,那么就要使用显式游标(对游标的讨论我们将在后面进行),INTO子句中要有与SELECT子句中相同列数量的变量。INTO子句中也可以是记录变量。 

    %TYPE属性 
         在PL/SQL中可以将变量和常量声明为内建或用户定义的数据类型,以引用一个列名,同时继承他的数据类型和大小。这种动态赋值方法是非常有用的,比如变量引用的列的数据类型和大小改变了,如果使用了%TYPE,那么用户就不必修改代码,否则就必须修改代码。 

    例: 
      v_empno SCOTT.EMP.EMPNO%TYPE; 
      v_salary EMP.SALARY%TYPE; 
       不但列名可以使用%TYPE,而且变量、游标、记录,或声明的常量都可以使用%TYPE。这对于定义相同数据类型的变量非常有用。 
        DELCARE 
        V_A NUMBER(5):=10; 
        V_B V_A%TYPE:=15; 
        V_C V_A%TYPE; 
        BEGIN 
          DBMS_OUTPUT.PUT_LINE 
          ('V_A='||V_A||'V_B='||V_B||'V_C='||V_C); 
        END 
        
        SQL>/ 
        V_A=10 V_B=15 V_C= 
         PL/SQL procedure successfully completed. 
         SQL> 
         
    其他DML语句 
        其它操作数据的DML语句是:INSERT、UPDATE、DELETE和LOCK TABLE,这些语句在PL/SQL中的语法与在SQL中的语法相同。我们在前面已经讨论过DML语句的使用这里就不再重复了。在DML语句中可以使用任何在DECLARE部分声明的变量,如果是嵌套块,那么要注意变量的作用范围。 

    例: 
      CREATE OR REPLACE PROCEDURE FIRE_EMPLOYEE (pempno in number) 
       AS 
        v_ename EMP.ENAME%TYPE; 
        BEGIN 
         SELECT ename INTO v_ename 
          FROM emp 
          WHERE empno=p_empno; 
          INSERT INTO FORMER_EMP(EMPNO,ENAME) 
          VALUES (p_empno,v_ename); 
          DELETE FROM emp 
          WHERE empno=p_empno; 
          UPDATE former_emp 
          SET date_deleted=SYSDATE 
          WHERE empno=p_empno; 
          
        EXCEPTION 
           WHEN NO_DATA_FOUND THEN 
           DBMS_OUTPUT.PUT_LINE('Employee Number Not Found!'); 
        END 

    DML语句的结果 
        当执行一条DML语句后,DML语句的结果保存在四个游标属性中,这些属性用于控制程序流程或者了解程序的状态。当运行DML语句时,PL/SQL打开一个内建游标并处理结果,游标是维护查询结果的内存中的一个区域,游标在运行DML语句时打开,完成后关闭。隐式游标只使用SQL%FOUND,SQL%NOTFOUND,SQL%ROWCOUNT三个属性.SQL%FOUND,SQL%NOTFOUND是布尔值,SQL%ROWCOUNT是整数值。 

    SQL%FOUND和SQL%NOTFOUND 
        在执行任何DML语句前SQL%FOUND和SQL%NOTFOUND的值都是NULL,在执行DML语句后,SQL%FOUND的属性值将是: 

    . TRUE :INSERT 
    . TRUE :DELETE和UPDATE,至少有一行被DELETE或UPDATE. 
    . TRUE :SELECT INTO至少返回一行 
    当SQL%FOUND为TRUE时,SQL%NOTFOUND为FALSE。 

    SQL%ROWCOUNT 
       在执行任何DML语句之前,SQL%ROWCOUNT的值都是NULL,对于SELECT             INTO语句,如果执行成功,SQL%ROWCOUNT的值为1,如果没有成功,SQL%ROWCOUNT的值为0,同时产生一个异常NO_DATA_FOUND. 

    SQL%ISOPEN 
      SQL%ISOPEN是一个布尔值,如果游标打开,则为TRUE, 如果游标关闭,则为FALSE.对于隐式游标而言SQL%ISOPEN总是FALSE,这是因为隐式游标在DML语句执行时打开,结束时就立即关闭。 

    事务控制语句 
        事务是一个工作的逻辑单元可以包括一个或多个DML语句,事物控制帮助用户保证数据的一致性。如果事务控制逻辑单元中的任何一个DML语句失败,那么整个事务都将回滚,在PL/SQL中用户可以明确地使用COMMIT、ROLLBACK、SAVEPOINT以及SET TRANSACTION语句。 
         COMMIT语句终止事务,永久保存数据库的变化,同时释放所有LOCK,ROLLBACK终止现行事务释放所有LOCK,但不保存数据库的任何变化,SAVEPOINT用于设置中间点,当事务调用过多的数据库操作时,中间点是非常有用的,SET TRANSACTION用于设置事务属性,比如read-write和隔离级等。 

    显式游标 
        当查询返回结果超过一行时,就需要一个显式游标,此时用户不能使用select into语句。PL/SQL管理隐式游标,当查询开始时隐式游标打开,查询结束时隐式游标自动关闭。显式游标在PL/SQL块的声明部分声明,在执行部分或异常处理部分打开,取数据,关闭。 

    使用游标 
        这里要做一个声明,我们所说的游标通常是指显式游标,因此从现在起没有特别指明的情况,我们所说的游标都是指显式游标。要在程序中使用游标,必须首先声明游标。 

    声明游标 
    语法: 
        CURSOR cursor_name IS select_statement; 

    在PL/SQL中游标名是一个未声明变量,不能给游标名赋值或用于表达式中。 

    例: 
        DELCARE 
        CURSOR C_EMP IS SELECT empno,ename,salary 
        FROM emp 
        WHERE salary>2000 
        ORDER BY ename; 
        ........ 
        BEGIN 
        在游标定义中SELECT语句中不一定非要表可以是视图,也可以从多个表或视图中选择的列,甚至可以使用*来选择所有的列 。 
        
    打开游标 
    使用游标中的值之前应该首先打开游标,打开游标初始化查询处理。打开游标的语法是: 
        OPEN cursor_name 
           cursor_name是在声明部分定义的游标名。 
        
    例: 
         OPEN C_EMP; 
            
    关闭游标 
    语法: 
         CLOSE cursor_name 
        
    例: 
         CLOSE C_EMP; 

    从游标提取数据 
        从游标得到一行数据使用FETCH命令。每一次提取数据后,游标都指向结果集的下一行。语法如下: 
         FETCH cursor_name INTO variable[,variable,...] 
         对于SELECT定义的游标的每一列,FETCH变量列表都应该有一个变量与之相对应,变量的类型也要相同。 

    例: 
       SET SERVERIUTPUT ON 
       DECLARE 
         v_ename EMP.ENAME%TYPE; 
         v_salary EMP.SALARY%TYPE; 
         CURSOR c_emp IS SELECT ename,salary FROM emp; 
         BEGIN 
           OPEN c_emp; 
              FETCH c_emp INTO v_ename,v_salary; 
                DBMS_OUTPUT.PUT_LINE('Salary of Employee'|| v_ename ||'is'|| v_salary); 
              FETCH c_emp INTO v_ename,v_salary; 
                DBMS_OUTPUT.PUT_LINE('Salary of Employee'|| v_ename ||'is'|| v_salary); 
              FETCH c_emp INTO v_ename,v_salary; 
                DBMS_OUTPUT.PUT_LINE('Salary of Employee'|| v_ename ||'is'|| v_salary); 
           CLOSE c_emp; 
         END 
         
        这段代码无疑是非常麻烦的,如果有多行返回结果,可以使用循环并用游标属性为结束循环的条件,以这种方式提取数据,程序的可读性和简洁性都大为提高,下面我们使用循环重新写上面的程序: 
    SET SERVERIUTPUT ON 
    DECLARE 
    v_ename EMP.ENAME%TYPE; 
    v_salary EMP.SALARY%TYPE; 
    CURSOR c_emp IS SELECT ename,salary FROM emp; 
    BEGIN 
      OPEN c_emp; 
        LOOP 
          FETCH c_emp INTO v_ename,v_salary; 
          EXIT WHEN c_emp%NOTFOUND; 
          DBMS_OUTPUT.PUT_LINE('Salary of Employee'|| v_ename ||'is'|| v_salary); 
    END 

    记录变量 
        定义一个记录变量使用TYPE命令和%ROWTYPE,关于%ROWsTYPE的更多信息请参阅相关资料。 
        记录变量用于从游标中提取数据行,当游标选择很多列的时候,那么使用记录比为每列声明一个变量要方便得多。 
        当在表上使用%ROWTYPE并将从游标中取出的值放入记录中时,如果要选择表中所有列,那么在SELECT子句中使用*比将所有列名列出来要安全得多。 

    例: 
    SET SERVERIUTPUT ON 
    DECLARE 
    R_emp EMP%ROWTYPE; 
    CURSOR c_emp IS SELECT * FROM emp; 
    BEGIN 
      OPEN c_emp; 
       LOOP 
         FETCH c_emp INTO r_emp; 
         EXIT WHEN c_emp%NOTFOUND; 
         DBMS_OUT.PUT.PUT_LINE('Salary of Employee'||r_emp.ename||'is'|| r_emp.salary); 
       END LOOP; 
      CLOSE c_emp; 
    END; 

    %ROWTYPE也可以用游标名来定义,这样的话就必须要首先声明游标: 

    SET SERVERIUTPUT ON 
    DECLARE 
    CURSOR c_emp IS SELECT ename,salary FROM emp; 
    R_emp c_emp%ROWTYPE; 
    BEGIN 
     OPEN c_emp; 
      LOOP 
        FETCH c_emp INTO r_emp; 
        EXIT WHEN c_emp%NOTFOUND; 
        DBMS_OUT.PUT.PUT_LINE('Salary of Employee'||r_emp.ename||'is'|| r_emp.salary); 
      END LOOP; 
     CLOSE c_emp; 
    END; 

    带参数的游标 
        与存储过程和函数相似,可以将参数传递给游标并在查询中使用。这对于处理在某种条件下打开游标的情况非常有用。它的语法如下: 

    CURSOR cursor_name[(parameter[,parameter],...)] IS select_statement; 

    定义参数的语法如下: 
       Parameter_name [IN] data_type[{:=|DEFAULT} value] 

      与存储过程不同的是,游标只能接受传递的值,而不能返回值。参数只定义数据类型,没有大小。 
       另外可以给参数设定一个缺省值,当没有参数值传递给游标时,就使用缺省值。游标中定义的参数只是一个占位符,在别处引用该参数不一定可靠。 

    在打开游标时给参数赋值,语法如下: 

    OPEN cursor_name[value[,value]....]; 
    参数值可以是文字或变量。 

    例: 
    DECALRE 
    CURSOR c_dept IS SELECT * FROM dept ORDER BY deptno; 
    CURSOR c_emp (p_dept VARACHAR2) IS 
      SELECT ename,salary 
      FROM emp 
      WHERE deptno=p_dept 
      ORDER BY ename 
    r_dept DEPT%ROWTYPE; 
    v_ename EMP.ENAME%TYPE; 
    v_salary EMP.SALARY%TYPE; 
    v_tot_salary EMP.SALARY%TYPE; 
    BEGIN 
      OPEN c_dept; 
         LOOP 
            FETCH c_dept INTO r_dept; 
            EXIT WHEN c_dept%NOTFOUND; 
            DBMS_OUTPUT.PUT_LINE('Department:'|| r_dept.deptno||'-'||r_dept.dname); 
            v_tot_salary:=0; 
            OPEN c_emp(r_dept.deptno); 
                LOOP 
                   FETCH c_emp INTO v_ename,v_salary; 
                   EXIT WHEN c_emp%NOTFOUND; 
                   DBMS_OUTPUT.PUT_LINE('Name:'|| v_ename||' salary:'||v_salary); 
                   v_tot_salary:=v_tot_salary+v_salary; 
                END LOOP; 
            CLOSE c_emp; 
            DBMS_OUTPUT.PUT_LINE('Toltal Salary for dept:'|| v_tot_salary); 
         END LOOP; 
      CLOSE c_dept; 
    END; 

    游标FOR循环 
    在大多数时候我们在设计程序的时候都遵循下面的步骤: 
    1、打开游标 
    2、开始循环 
    3、从游标中取值 
    4、检查那一行被返回 
    5、处理 
    6、关闭循环 
    7、关闭游标 
        可以简单的把这一类代码称为游标用于循环。但还有一种循环与这种类型不相同,这就是FOR循环,用于FOR循环的游标按照正常的声明方式声明,它的优点在于不需要显式的打开、关闭、取数据,测试数据的存在、定义存放数据的变量等等。游标FOR循环的语法如下: 

    FOR record_name IN 
     (corsor_name[(parameter[,parameter]...)] 
     | (query_difinition) 
    LOOP 
      statements 
    END LOOP; 

    下面我们用for循环重写上面的例子: 
    DECALRE 
    CURSOR c_dept IS SELECT deptno,dname FROM dept ORDER BY deptno; 
    CURSOR c_emp (p_dept VARACHAR2) IS 
     SELECT ename,salary 
     FROM emp 
     WHERE deptno=p_dept 
     ORDER BY ename 
    v_tot_salary EMP.SALARY%TYPE; 
    BEGIN 
       FOR r_dept IN c_dept LOOP 
         DBMS_OUTPUT.PUT_LINE('Department:'|| r_dept.deptno||'-'||r_dept.dname); 
         v_tot_salary:=0; 
         FOR r_emp IN c_emp(r_dept.deptno) LOOP 
        DBMS_OUTPUT.PUT_LINE('Name:' || v_ename || 'salary:' || v_salary);  
        v_tot_salary:=v_tot_salary+v_salary; 
         END LOOP; 
         DBMS_OUTPUT.PUT_LINE('Toltal Salary for dept:'|| v_tot_salary); 
      END LOOP; 
    END; 

    在游标FOR循环中使用查询 
        在游标FOR循环中可以定义查询,由于没有显式声明所以游标没有名字,记录名通过游标查询来定义。 
    DECALRE 
     v_tot_salary EMP.SALARY%TYPE; 
    BEGIN 
      FOR r_dept IN (SELECT deptno,dname FROM dept ORDER BY deptno) LOOP 
         DBMS_OUTPUT.PUT_LINE('Department:'|| r_dept.deptno||'-'||r_dept.dname); 
         v_tot_salary:=0; 
         FOR r_emp IN (SELECT ename,salary 
                   FROM emp 
                   WHERE deptno=p_dept 
                   ORDER BY ename) LOOP 
           DBMS_OUTPUT.PUT_LINE('Name:'|| v_ename||' salary:'||v_salary); 
           v_tot_salary:=v_tot_salary+v_salary; 
         END LOOP; 
      DBMS_OUTPUT.PUT_LINE('Toltal Salary for dept:'|| v_tot_salary); 
      END LOOP; 
    END; 

    游标中的子查询 
        语法如下: 
        
    CURSOR C1 IS SELECT * FROM emp 
      WHERE deptno NOT IN (SELECT deptno 
       FROM dept 
       WHERE dname!='ACCOUNTING'); 
    可以看出与SQL中的子查询没有什么区别。 

    游标中的更新和删除 
        在PL/SQL中依然可以使用UPDATE和DELETE语句更新或删除数据行。显式游标只有在需要获得多行数据的情况下使用。PL/SQL提供了仅仅使用游标就可以执行删除或更新记录的方法。 
    UPDATE或DELETE语句中的WHERE CURRENT OF子串专门处理要执行UPDATE或DELETE操作的表中取出的最近的数据。要使用这个方法,在声明游标时必须使用FOR UPDATE子串,当对话使用FOR UPDATE子串打开一个游标时,所有返回集中的数据行都将处于行级(ROW-LEVEL)独占式锁定,其他对象只能查询这些数据行,不能进行UPDATE、DELETE或SELECT...FOR            UPDATE操作。 

    语法: 
        FOR UPDATE [OF [schema.]table.column[,[schema.]table.column].. 
        [nowait] 
        
        在多表查询中,使用OF子句来锁定特定的表,如果忽略了OF子句,那么所有表中选择的数据行都将被锁定。如果这些数据行已经被其他会话锁定,那么正常情况下ORACLE将等待,直到数据行解锁。 

    在UPDATE和DELETE中使用WHERE CURRENT OF子串的语法如下: 

    WHERE{CURRENT OF cursor_name|search_condition} 

    例: 
    DELCARE 
    CURSOR c1 IS SELECT empno,salary 
     FROM emp 
     WHERE comm IS NULL 
     FOR UPDATE OF comm; 
    v_comm NUMBER(10,2); 
    BEGIN 
       FOR r1 IN c1 LOOP 
         IF r1.salary<500 THEN 
           v_comm:=r1.salary*0.25; 
         ELSEIF r1.salary<1000 THEN 
           v_comm:=r1.salary*0.20; 
         ELSEIF r1.salary<3000 THEN 
           v_comm:=r1.salary*0.15; 
         ELSE 
             v_comm:=r1.salary*0.12; 
         END IF; 
       UPDATE emp; 
       SET comm=v_comm 
       WHERE CURRENT OF c1l; 
       END LOOP; 
    END

    posted @ 2008-11-20 14:52 鸿雁 阅读(184) | 评论 (0)编辑 收藏

    触发器与存储过程

    触发器一 触发器介绍
    触发器是一种特殊的存储过程,它在插入,删除或修改特定表中
    的数据时触发执行,它比数据库本身标准的功能有更精细和更复杂的
    数据控制能力。数据库触发器有以下的作用:
    * 安全性。可以基于数据库的值使用户具有操作数据库的某种权利。
    # 可以基于时间限制用户的操作,例如不允许下班后和节假日
    修改数据库数据。
    # 可以基于数据库中的数据限制用户的操作,例如不允许股票
    的价格的升幅一次超过10%。
    * 审计。可以跟踪用户对数据库的操作。
    # 审计用户操作数据库的语句。
    # 把用户对数据库的更新写入审计表。
    * 实现复杂的数据完整性规则。
    # 实现非标准的数据完整性检查和约束。触发器可产生比规则
    更为复杂的限制。与规则不同,触发器可以引用列或数据库对
    象。例如,触发器可回退任何企图吃进超过自己保证金的期货。
    # 提供可变的缺省值。
    * 实现复杂的非标准的数据库相关完整性规则。触发器可以对数
    据库中相关的表进行连环更新。例如,在auths表author_code列上的
    删除触发器可导致相应删除在其它表中的与之匹配的行。
    # 在修改或删除时级联修改或删除其它表中的与之匹配的行。
    # 在修改或删除时把其它表中的与之匹配的行设成NULL值。
    # 在修改或删除时把其它表中的与之匹配的行级联设成缺省值。
    # 触发器能够拒绝或回退那些破坏相关完整性的变化,取消试
    图进行数据更新的事务。当插入一个与其主健不匹配的外部键
    时,这种触发器会起作用。例如,可以在books.author_code
    列上生成一个插入触发器,如果新值与auths.author_code列
    中的某值不匹配时,插入被回退。
    * 同步实时地复制表中的数据。
    * 自动计算数据值,如果数据的值达到了一定的要求,则进行特
    定的处理。例如,如果公司的帐号上的资金低于5万元则立即给财务人
    员发送警告数据。
    ORACLE与SYBASE数据库的触发器有一定的区别,下面将分别讲述
    这两种数据库触发器的作用和写法。
    二 ORACLE 触发器
    ORACLE产生数据库触发器的语法为:
    create [or replace] trigger 触发器名 触发时间 触发事件
    on 表名
    [for each row]
    pl/sql 语句
    其中:
    触发器名:触发器对象的名称。由于触发器是数据库自动执行
    的,因此该名称只是一个名称,没有实质的用途。
    触发时间:指明触发器何时执行,该值可取:
    before---表示在数据库动作之前触发器执行;
    after---表示在数据库动作之后出发器执行。
    触发事件:指明哪些数据库动作会触发此触发器:
    insert:数据库插入会触发此触发器;
    update:数据库修改会触发此触发器;
    delete:数据库删除会触发此触发器。
    表 名:数据库触发器所在的表。
    for each row:对表的每一行触发器执行一次。如果没有这一
    选项,则只对整个表执行一次。
    举例:下面的触发器在更新表auths之前触发,目的是不允许在
    周末修改表:
    create trigger auth_secure
    before insert or update or delete //对整表更新前触发
    on auths
    begin
    if(to_char(sysdate,'DY')='SUN'
    RAISE_APPLICATION_ERROR(-20600,'不能在周末修改表auths');
    end if;
    end
    三 SYBASE数据库触发器
    SYBASE数据库触发器的作用与ORACLE非常类似,仅有较小的差异。
    SYBASE产生触发器的语法为:
    CREATE TRIGGER 触发器名
    ON 表名
    FOR INSERT,UPDATE,DELETE
    AS
    SQL_statement |
    FOR INSERT,UPDATE
    AS
    IF UPDATE(column_name) [AND|OR UPDATE(column_name)]...
    SQL_statements
    上面FOR子句用来指定在触发器上的哪些数据更新命令可激活该
    触发器。IF UPDATE子句检查对指定列的操作类型,在IF UPDATE子句
    中可指定多个列。
    与ORACLE不同,对于每条SQL语句,触发器只执行一次。触发器
    在数据更新语句完成以后立即执行。触发器和启动它的语句被当作一
    个事务处理,事务可以在触发器中回退。
    下面举例说明SYBASE触发器的写法。
    create trigger forinsert_books
    on books
    for insert
    as
    if(select count(*) from auths,inserted
    where auths.author_code=insert.author_code)!=@@rowcount
    begin
    rollback transaction
    print "books 表中 author_code 列的值在auths 表中不存在。"
    end

    ----------------------------------------------------------------------------------------------------------
    存储过程一 存储过程介绍
    存储过程是由流控制和SQL语句书写的过程,这个过程经编译和优化
    后存储在数据库服务器中,使用时只要调用即可。在ORACLE中,若干个
    有联系的过程可以组合在一起构成程序包。
    使用存储过程有以下的优点:
    * 存储过程的能力大大增强了SQL语言的功能和灵活性。存储过程可
    以用流控制语句编写,有很强的灵活性,可以完成复杂的判断和较复杂的
    运算。
    * 可保证数据的安全性和完整性。
    # 通过存储过程可以使没有权限的用户在控制之下间接地存取数据
    库,从而保证数据的安全。
    # 通过存储过程可以使相关的动作在一起发生,从而可以维护数据
    库的完整性。
    * 再运行存储过程前,数据库已对其进行了语法和句法分析,并给出
    了优化执行方案。这种已经编译好的过程可极大地改善SQL语句的性能。
    由于执行SQL语句的大部分工作已经完成,所以存储过程能以极快的速度执
    行。
    * 可以降低网络的通信量。
    * 使体现企业规则的运算程序放入数据库服务器中,以便:
    # 集中控制。
    # 当企业规则发生变化时在服务器中改变存储过程即可,无须修改
    任何应用程序。企业规则的特点是要经常变化,如果把体现企业规则的运
    算程序放入应用程序中,则当企业规则发生变化时,就需要修改应用程序
    工作量非常之大(修改、发行和安装应用程序)。如果把体现企业规则的
    运算放入存储过程中,则当企业规则发生变化时,只要修改存储过程就可
    以了,应用程序无须任何变化。
    不同数据库存储过程的写法不一,在后面的讲座中将分别介绍ORACLE
    和SYBASE存储过程的用法。
    二 ORACLE 的存储过程
    ORACLE 创建存储过程的语法为:
    create [or replace] procedure 过程名
    参数1 [in|out|in out] 数据类型
    [,参数2 [in|out|in out] 数据类型]...
    {is|as} pl/sql 语句
    下面举例说明ORACLE数据库存储过程的写法和用法。
    可以建立一个存储过程,每当用户修改数据库的重要数据时,即把
    用户的用户名、日期和操作类型记录下来:
    create procedure update_log is
    begin
    insert into update_log_tab(use_name,update_date,operation)
    values(user,sysdate,'update'
    end;
    可以在恰当的位置调用这个存储过程来记录用户对表的修改。例如下面在
    表sal_comm上建立一个修改触发器,每当用户修改此表后,用户的名称、修改
    时间和操作即被记录在了表update_log_tab中:
    create trigger audit_update
    after update on sal_comm
    for each row
    begin
    update_log
    end
    三 Sybase的存储过程
    尽管Sybase存储过程的功能和写法与ORACLE类似,但他们之间还是
    有一定的差别。下面讲述SYBASE的存储过程。
    SYBASE可以用CREATE PROCedure命令生成存储过程:
    CREATE PROCedure 存储过程名 [;number]
    [[(] @parameter_name datatype [=default] [OUTput]
    [, @parameter_name datatype [=default] [OUTput]]...[)]]
    [WITH RECOMPILE]
    AS SQL_statements
    下面是一个查询作者编码、名称和生日的存储过程:
    create proc p_auths @author_code varchar(10)
    as
    select author_code, name, birthdate
    from auths
    where author_code=@author_code
    下面执行过程p_auths:
    p_auths @author_code=A00001
    在CREATE PROC语句中,可以为参数赋缺省值,该值可以是任何常量。
    当用户不提供参数值时,该值便作为参数值提供给过程。

    Sybase的存储过程是集中存储在SQL Server中的预先定义且已经编译好的事务。存储过程由SQL语句和流程控制语句组成。
    它的功能包括:接受参数;调用另一过程;返回一个状态值给调用过程或批处理,指示调用成功或失败;返回若干个参数值给调
    用过程或批处理,为调用者提供动态结果;在远程SQL Server中运行等。
      存储过程的性能特
      1.存储过程是预编译过的,这就意味着它与普通的SQL语句或批处理的SQL语句不同。当首次运行一个存储过程时,SQL Server的查询处理器对其进行分析,在排除了语法错误之后形成存储在系统中的可执行方案。由于查询处理的大部分工作已经完成,所以存储过程执行速度很快。
      2.存储过程和待处理的数据都放在同一台运行SQL Server的计算机上,使用存储过程查询当地的数据,效率自然很高。
      3.存储过程一般多由Client端通过存储过程的名字进行调用,即跨网传送的只是存储过程的名字及少量的参数(如果有的话),而不是构成存储过程的许多SQL语句,因此可以减少网络传输量,加快系统响应速度。
      4.存储过程还有着如同C语言子函数那样的被调用和返回值的方便特性。所以,存储过程大大增强了SQL语言的功能、效率和灵活性。掌握和应用好存储过程,对进一步发挥Sybase数据库系统的强大功能有着重要的意义。
      存储过程的语法规则
      建立存储过程的语法规则为:
      CREATE PROCedure[owner.]procedurename[;number]
      [[(]@parameter_name datatype[=default][OUTput]
      [,@parameter_name datatype[=default][OUTput]]...[)]]
      [WITH RECOMPILE]
      AS SQL_statements
      使用存储过程的语法规则为:
      [EXECute][@return-status=]
      [[[server.]database.]owner.]procedurename[;number]
      [[@parameter_name=]value|[@parameter_name=]@varialbe[OUT put]
      [,[@parameter_name=]value|[@parameter_name=]@variable[OU Tput]...]]
      [WITH RECOMPILE]
      下面简要介绍这两个命令的常用选项以及建立和使用存储过程的要点,关于选项的更为详细的说明请参考有关手册。
      1.[[[server.]database.]owner.]procedure_name:存储过程的名字。
      2.@parameter_name datatype[=default][OUTput]:形式参数(形参)的名称、类型。dfault是赋予的缺省值(可选),OUTput指定本参数为输出参数(可选)。形参是存储过程中的自变量,可以有多个,名字必须以@打头,最长30个字符。
      3.SQL_statements:定义存储过程功能的SQL语句。
      4.@return_status:接受存储过程返回状态值的变量。
      5.[@parameter_name=]value:实际参数(实参),@parameter_name 为实参的名称(可选)。如果某个实参以@parameter_name=value提供, 那么随后的实参也都要采用这一形式提供。
      6.[@parameter_name=]@varialbe[OUTput]:将变量@varialbe中的值作为实参传递给形参@parameter_name(可选),如果变量@varialb e是用来接受返回的参数值,则选项OUTput不可缺少。
      
      存储过程的建立和使用
      我们将通过几个例子进行介绍。
      假设有一个用下述语句生成的技能工资表RS_LS_GZ_JiNeng:
      create table RS_LS_GZ_JiNeng /*技能工资表*/
      (GeRen_id char(4), /*个人代码*/
      RiQi smalldatetime, /*执行日期*/
      YuanYin_id char(1) null, /*变动原因代码*/
      JinE smallmoney) /*技能工资金额*/
      该表存储着某单位员工多年来技能工资的历史档案。
      例1.如果要查询全体员工的技能工资变动历史,则可先建立一个存储过程p_RsGz_JiNeng_All:
      create procedure p_RsGz_JiNeng_All
      as
      select *
      from RS_LS_GZ_JiNeng
      order by GeRenid,RiQi
      然后用批处理语句调用存储过程p_RsGz_JiNeng_All进行查询:
      execute p_RsGz_JiNeng_All
      本例只显示查询到的数据,无输入、输出参量,是最简单的一个存储过程。
      例2.如果要查询某人技能工资的变动历史,可建立另一个存储过程p_RsGz_JiNeng:
      create procedure p_RsGz_JiNeng
      @c_GeRenId char(4)
      as
      select *from RS_LS_GZ_JiNeng
      where GeRen_id=@c_GeRenId
      order by RiQi
      之后用批处理语句调用存储过程p_Rs_Gz_JiNeng进行查询:
      declare @GeRenId char(4)
      select @GeRenId="0135" /*设要查询员工的个人代码为"0135" */
      execute p_RsGz_JeNeng @c_GeRenId=@GeRenId
      存储过程p_RsGz_JiNeng中定义了一个形参@c_GeRenId,是字符型变量。在调用该过程的批处理中,既可以用具体的值也可以用变量作为实参。用变量作实参(如本例)时,必须用delare语句加以说明。注意,在批处理的调用过程语句@c_GeRenId=@GeRenId中的@c_GeRenId是存储过程p_RsGz_JiNeng中的形参名,不是批处理中的变量,所以不能将它列入d eclare语句的变量单中。
      例3.如果要计算当月工资,就必须从工资历史中查出员工距离当前最近的一次技能工资变动的结果:
      create procedure p_RsGz_JiNeng_Slt
      (@c_GeRenId char(4),@sm_JinE smallmoney output)
      as
      select @sm_JinE=JinE
      from RS_LS_GZ_JiNeng
      where RiQi=(select max(RiQi)
      from RS_LS_GZ_JiNeng
      where GeRenid=@c-GeRenId)/*找出历史记录中距离当前最近的日期*/
      调用存储过程p_RsGz_JiNeng_Slt进行查询:
      declare @GeRenId char(4),@JinE smallmoney
      select @GeRenid="0135"/*设要查询员工的个人代码为"0135"*/
      select @JinE=0
      execute p_RsGz_JiNeng_slt @c_GeRenId=@GeRenId,@sm_JinE=@ JinE output
      这里,变量@JinE用来存储过程形参@sm_JinE传回的金额。在调用过程语句中,@sm_JiE = @JinE output中的output不可省略。否则, 变量@JinE将得不到形参传回的数值而始终为零(等于初值)。
      例4.查到了个人代码为"0135"员工的技能工资就显示其历史纪录,查不到则显示一条出错信息。
      create procedure p_RsGz_JiNeng_Rtn
      @c_GeRenId char(4)
      as
      declare @ErrCode smallint
      select @ErrCode=0
      if exists(select* from RS-LS-GZ-JiNeng
      where GeRenid=@c-GeRenId)
      begin
      select *
      from RS_LS_GZ_JiNeng
      whrer GeRen_id=@c_GeRenId
      order by RiQi
      return @ErrCode
      end
      esle
      begin
      select @ErrCode=1
      return @ErrCode
      end
      调用存储过程p_RsGz_JiNeng_Rtn:
      declare @GeRenId char(4),@RtnCode smallint
      select @GeRenId="0135"
      select @RtnCode=0
      execute @RtnCode=p_RsGz_JiNeng_Rtn @c_GeRenId=@GeRenId
      if @RtnCode=1
      print"No this one!"
      存储过程p_RsGz_JiNeng_Rtn向调用者返回一个存储在变量@ErrC ode里的值,这个值被称为状态值,它向调用者反映存储过程执行的成败状态。在本例中,如果查不到指定员工技能工资的任何记录时,就认为"查无此人",返回出错状态值1。否则,返回成功状态值0。
      调用过程的批处理语句使用变量@RtnCode存储返回的状态值,一旦检出存储过程p_RsG_ JiNeng_Rtn返回了错误标志(@RtnCode=1),就显示一条信息"No this one!"。
      小结
      上述四个例子简要介绍了存储过程常用的几种形式,从中我们已经可以领略到它的编程特色以及使用上的灵活性和方便性。
      虽然上述例子在调用存储过程时都是用SQL的批处理语句实现的, 但并不意味着这是唯一的方法。例如在存储过程中调用存储过程(即所谓过程嵌套)的现象就很常见。另外,在其它Sybase数据库开发系统(如PowerBuilder)的script语句中调用Sybase的存储过程也非常普遍。

    posted @ 2008-11-20 14:35 鸿雁 阅读(867) | 评论 (0)编辑 收藏

    如何把大部分表中的数据迁移到另一个数据库中

    需求描述:目前远程服务器有两个数据库AA和BB,两个数据库的数据库版本都为10.2.1.0.1,两个数据库中的用户不一样,但数据库中的表结构都是一样的,数据库AA表中的数据比数据库BB表中的数据多很多,现在需要把数据库AA中几十张表的数据迁移到数据库BB的表中。如果在数据库AA中存在的数据,但在数据库BB中没有的数据,则需要把这些数据导入到数据库BB中。


      比如:数据库AA 的数据库名称为AA,所有的表都在A_UserName用户下,改用户的密码为A_Password;
          数据库BB 的数据库名称为BB,所有的表都在B_UserName用户下,改用户的密码为B_Password;


      假设本机是一台windows系统的PC机,装有Oracle数据库10.2.1.0.1的版本(服务器版或客户端版都可以),在该系统已经配置好数据库的监听器($ORACLE_HOME\NETWORK\ADMIN\tnsnames.ora).
    下面导出、导入两个表为例子(当然几十个表也是没有问题的)

    1 导出数据库AA中两表的所有数据:


    D:\>expA_UserName/A_Password@AAtables=(DH_REP_WORK,DRILL_ADM_YEAR_TARGET) file=20081029  log=20081029.log
    Export: Release 10.2.0.1.0 - Production on 星期三 10月 29 16:11:20 2008
    Copyright (c) 1982, 2005, Oracle.  All rights reserved.


    连接到: Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit Production With the Partitioning, OLAP and Data Mining options

    已导出 ZHS16GBK 字符集和 AL16UTF16 NCHAR 字符集


    即将导出指定的表通过常规路径...
    . . 正在导出表                     DH_REP_WORK导出了           1 行
    . . 正在导出表           DRILL_ADM_YEAR_TARGET导出了          40 行
    成功终止导出, 没有出现警告。

    从上面可以看出来成功导出来远程数据库AA中两表的数据。

    2  导入到数据库BB中(请先看下面的错误原因)

    D:\>impB_UserName/B_Password@BBtables=(DH_REP_WORK,DRILL_ADM_YEAR_TARGET)  file=20081029  log=20081029imp.log

    Import: Release 10.2.0.1.0 - Production on 星期三 10月 29 16:13:33 2008

    Copyright (c) 1982, 2005, Oracle.  All rights reserved.


      连接到: Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit Production With the Partitioning, OLAP and Data Mining options

    经由常规路径由 EXPORT:V10.02.01 创建的导出文件IMP-00013: 只有 DBA 才能导入由其他 DBA 导出的文件IMP-00000: 未成功终止导入

    不能导入原来是B_UserName用户没有具有DBA角色的权限。

    3、登陆到数据库BB中,用超级用户sys给B_UserName用户授权


    SQL> grant dba to B_UserName;
    Grant succeeded.

    4 、现在重新导入到数据库BB中,为了减少日志的错误使用ignore=yes(忽略错误),rows=y是导入表中的数据,grants=no表示不导入授权(假如在数据库AA中表DH_REP_WORK授权给了test用户,但数据库BB中没有test用户,则不用执行授权给test用户)。

    D:\>imp   B_UserName/B_Password@BB   tables=(DH_REP_WORK,DRILL_ADM_YEAR_TARGET)   rows=y ignore=yes  grants=no  file=20081029   log=20081029Imp.log

    执行上面的语句,可能会有些错误提示,一般是在数据库BB中的已经存在该数据,由于主键的唯一性,所以不能插入。但数据库AA中存在、但数据库BB中不存在的数据就可以顺利的插入到数据库BB中了。

    5、登陆到数据库BB中,用超级用户sys回收B_UserName用户dba的角色:


    SQL> revoke dba from B_UserName;
    Revoke succeeded.

    posted @ 2008-11-13 14:47 鸿雁 阅读(605) | 评论 (0)编辑 收藏

    Oracle的Nvl函数

    从两个表达式返回一个非 null 值。

    语法

    NVL(eExpression1, eExpression2)

    参数
    eExpression1, eExpression2

    如果 eExpression1 的计算结果为 null 值,则 NVL( ) 返回 eExpression2。如果 eExpression1 的计算结果不是 null 值,则返回 eExpression1。eExpression1 和 eExpression2 可以是任意一种数据类型。如果 eExpression1 与 eExpression2 的结果皆为 null 值,则 NVL( ) 返回 .NULL.。

    返回值类型

    字符型、日期型、日期时间型、数值型、货币型、逻辑型或 null 值

    说明

    在不支持 null 值或 null 值无关紧要的情况下,可以使用 NVL( ) 来移去计算或操作中的 null 值。

    select nvl(a.name,'空得') as name from student a join school b on a.ID=b.ID

    注意:两个参数得类型要匹配

    实际的写法可以这样,如果在对材料做入库的时候有个入库金额的字段rkje,然后在做累加操作的时候可以这样写:
    rkje=nvl(rkje,0.00)+"+rkje2_1+",表示如果开始数据库值是null的话就用0.00表示,然后再累加新的数据rkje2_1。

    posted @ 2008-10-31 11:13 鸿雁 阅读(1249) | 评论 (0)编辑 收藏

    自定义处理JS的空格

    去掉字符串的前后空格 

    代码 :

    function trim(str){
      return str.replace(/^\ +|\ +$/ig,"");
    }

    posted @ 2008-10-30 15:39 鸿雁 阅读(392) | 评论 (1)编辑 收藏

    JAVA实现四舍五入的方法

    Java实现四舍五入方法

     

    方法一 : 

    double source = 3.129;

    java.text.DecimalFormat df = new java.text.DecimalFormat("#.00");   // 保留小数点后两位   

    String aveprice=df.format(source);     

     

    方法二 : 

    double source = 3.129;

    BigDecimal bd = new BigDecimal(source);

    bd.setScale(2,BigDecimal.ROUND_HALF_EVEN).floatValue() ;  // 保留小数点后两位

    posted @ 2008-10-30 15:37 鸿雁 阅读(390) | 评论 (0)编辑 收藏

    各种数据库取表前N条记录的写法

     

    各种数据库取表前N条记录的写法:

    1Oracle

    Select * from table where rowNum<=n

    2.Informix

    Select first n* from table

    3.DB2

    Select * row_number() over(order by col1 desc) as rownum where rownum<=n

     DB2

    Select column from table fetch first n rows only

    4.SqlServer

    Select top n* from table

    5.sysbase

    Select top n* from table

    6.mysql

    Select * from table limit n

    posted @ 2008-10-13 10:15 鸿雁 阅读(473) | 评论 (1)编辑 收藏

    数据库设计方法、规范与技巧

         摘要:   阅读全文

    posted @ 2008-10-10 09:59 鸿雁 阅读(136) | 评论 (0)编辑 收藏

    模式与框架

    1.什么是模式?
    模式,即pattern,其实就是解决某一类问题的方法论,你把解决某类问题的方法总结归纳到理论高度,这就是模式。
     
    Alexander给出的经典定义是:每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心。通过这种方式,你可以无数次地使用那些已有的解决方案,无需再重复相同的工作。

    模式有不同的领域,建筑领域有建筑模式,软件设计领域也有设计模式。当一个领域逐渐成熟的时候,自然会出现很多模式。

    什么是框架?

    框架,即framework。其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单地说就是使用别人搭建好的舞台,你来做表演。而且,框架一般是成熟的,不断升级的软件。

    2。为什么要用模式?

    因为模式是一种指导,在一个良好的指导下,有助于你完成任务,有助于你作出一个优良的设计方案,达到事半功倍的效果。而且会得到解决问题的最佳办法。

    为什么要使用框架?

    因为软件系统发展到今天已经很复杂了,特别是服务器端软件,设计到的知识,内容,问题太多。在某些方面使用别人成熟的框架,就相当于让别人帮你完成一些基础的工作,你只需要集中精力完成系统业务逻辑设计。而且框架一般是成熟,稳健的,它可以处理系统很多细节的问题,比如,事物处理,安全性,数据流控制等问题。还有框架一般都是经过很多人使用,所以结构很好,所以扩展性也好,而且它是不断升级的,你可以直接享受别人升级代码带来的好处。

    框架一般处在低层的应用平台(如J2EE)和高层业务逻辑之间的中间层。

    软件为什么要分层?

    为了实现“高内聚、低耦合”。把问题划分开来各个解决,易于控制,易于扩展,易于分配资源。。。



    posted @ 2008-10-10 09:39 鸿雁 阅读(237) | 评论 (0)编辑 收藏

    Javascript中最常用的55个经典技巧

    1. oncontextmenu="window.event.returnValue=false" 将彻底屏蔽鼠标右键
    <table border oncontextmenu=return(false)><td>no</table> 可用于Table

    2. <body onselectstart="return false"> 取消选取、防止复制

    3. onpaste="return false" 不准粘贴

    4. oncopy="return false;" oncut="return false;" 防止复制

    5. <link rel="Shortcut Icon" href="favicon.ico"> IE地址栏前换成自己的图标

    6. <link rel="Bookmark" href="favicon.ico"> 可以在收藏夹中显示出你的图标


    7. <input style="ime-mode:disabled"> 关闭输入法


    8. 永远都会带着框架
    <script language="JavaScript"><!--
    if (window == top)top.location.href = "frames.htm"; //frames.htm为框架网页
    // --></script>


    9. 防止被人frame
    <SCRIPT LANGUAGE=JAVASCRIPT><!--
    if (top.location != self.location)top.location=self.location;
    // --></SCRIPT>


    10. 网页将不能被另存为
    <noscript><*** src="/*.html>";</***></noscript>


    11. <input type=button value="/查看网页源代码
    onclick="window.location = "view-source:"+ "http://www.pconline.com.cn"">
    12.删除时确认
    <a href=""javascript :if(confirm("确实要删除吗?"))location="boos.asp?&areyou=删除&page=1"">删除</a>


    13. 取得控件的绝对位置
    //Javascript
    <script language="Javascript">
    function getIE(e){
    var t=e.offsetTop;
    var l=e.offsetLeft;
    while(e=e.offsetParent){
    t+=e.offsetTop;
    l+=e.offsetLeft;
    }
    alert("top="+t+"/nleft="+l);
    }
    </script>
    //VBScript
    <script language="VBScript"><!--
    function getIE()
    dim t,l,a,b
    set a=document.all.img1
    t=document.all.img1.offsetTop
    l=document.all.img1.offsetLeft
    while a.tagName<>"BODY"
    set a = a.offsetParent
    t=t+a.offsetTop
    l=l+a.offsetLeft
    wend
    msgbox "top="&t&chr(13)&"left="&l,64,"得到控件的位置"
    end function
    --></script>


    14. 光标是停在文本框文字的最后
    <script language="javascript">
    function cc()
    {
    var e = event.srcElement;
    var r =e.createTextRange();
    r.moveStart("character",e.value.length);
    r.collapse(true);
    r.select();
    }
    </script>
    <input type=text name=text1 value="123" onfocus="cc()">


    15. 判断上一页的来源
    javascript :
    document.referrer


    16. 最小化、最大化、关闭窗口
    <object id=hh1 classid="clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11">
    <param name="Command" value="Minimize"></object>
    <object id=hh2 classid="clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11">
    <param name="Command" value="Maximize"></object>
    <OBJECT id=hh3 classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11">
    <PARAM NAME="Command" value="/Close"></OBJECT>
    <input type=button value="/最小化 onclick=hh1.Click()>
    <input type=button value="/blog/最大化 onclick=hh2.Click()>
    <input type=button value=关闭 onclick=hh3.Click()>
    本例适用于IE


    17.屏蔽功能键Shift,Alt,Ctrl
    <script>
    function look(){
    if(event.shiftKey)
    alert("禁止按Shift键!"); //可以换成ALT CTRL
    }
    document.onkeydown=look;
    </script>


    18. 网页不会被缓存
    <META HTTP-EQUIV="pragma" CONTENT="no-cache">
    <META HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate">
    <META HTTP-EQUIV="expires" CONTENT="Wed, 26 Feb 1997 08:21:57 GMT">
    或者<META HTTP-EQUIV="expires" CONTENT="0">


    19.怎样让表单没有凹凸感?
    <input type=text style="""border:1 solid #000000">

    <input type=text style="border-left:none; border-right:none; border-top:none; border-bottom:
    1 solid #000000"></textarea>


    20.<div><span>&<layer>的区别?
    <div>(division)用来定义大段的页面元素,会产生转行
    <span>用来定义同一行内的元素,跟<div>的唯一区别是不产生转行
    <layer>是ns的标记,ie不支持,相当于<div>


    21.让弹出窗口总是在最上面:
    <body onblur="this.focus();">


    22.不要滚动条?
    让竖条没有:
    <body style="overflow:scroll;overflow-y:hidden">
    </body>
    让横条没有:
    <body style="overflow:scroll;overflow-x:hidden">
    </body>
    两个都去掉?更简单了
    <body scroll="no">
    </body>


    23.怎样去掉图片链接点击后,图片周围的虚线?
    <a href="#" onFocus="this.blur()"><img src="/logo.jpg" border=0></a>


    24.电子邮件处理提交表单
    <form name="form1" method="post" action=mailto:****@***.com
    enctype="text/plain">
    <input type=submit>
    </form>


    25.在打开的子窗口刷新父窗口的代码里如何写?
    window.opener.location.reload()


    26.如何设定打开页面的大小
    <body onload="top.resizeTo(300,200);">
    打开页面的位置<body onload="top.moveBy(300,200);">


    27.在页面中如何加入不是满铺的背景图片,拉动页面时背景图不动
    <STYLE>
    body
    {background-image:url(/logo.gif); background-repeat:no-repeat;
    background-position:center;background-attachment: fixed}
    </STYLE>


    28. 检查一段字符串是否全由数字组成
    <script language="Javascript"><!--
    function checkNum(str){return str.match(//D/)==null}
    alert(checkNum("1232142141"))
    alert(checkNum("123214214a1"))
    // --></script>


    29. 获得一个窗口的大小
    document.body.clientWidth; document.body.clientHeight


    30. 怎么判断是否是字符
    if (/[^/x00-/xff]/g.test(s)) alert("含有汉字");
    else alert("全是字符");


    31.TEXTAREA自适应文字行数的多少
    <textarea rows=1 name=s1 cols=27 onpropertychange
    ="this.style.posHeight=this.scrollHeight">
    </textarea>


    32. 日期减去天数等于第二个日期
    <script language=Javascript>
    function cc(dd,dadd)
    {
    //可以加上错误处理
    var a = new Date(dd)
    a = a.valueOf()
    a = a - dadd * 24 * 60 * 60 * 1000
    a = new Date(a)
    alert(a.getFullYear() + "年" + (a.getMonth() + 1) + "月" + a.getDate() + "日")
    }
    cc("12/23/2002",2)
    </script>


    33. 选择了哪一个Radio
    <HTML><script language="vbscript">
    function checkme()
    for each ob in radio1
    if ob.checked then
    window.alert ob.value
    next
    end function
    </script><BODY>
    <INPUT name="radio1" type="radio" value="/style" checked>Style
    <INPUT name="radio1" type="radio" value="/blog/barcode">Barcode
    <INPUT type="button" value="check" onclick="checkme()">
    </BODY></HTML>


    34.脚本永不出错
    <SCRIPT LANGUAGE="JavaScript">
    <!-- Hide
    function killErrors() {
    return true;
    }
    window.onerror = killErrors;
    // -->
    </SCRIPT>


    35.ENTER键可以让光标移到下一个输入框
    <input onkeydown="if(event.keyCode==13)event.keyCode=9">


    36. 检测某个网站的链接速度:
    把如下代码加入<body>区域中:
    <script language=Javascript>
    tim=1
    setInterval("tim++",100)
    b=1
    var autourl=new Array()
    autourl[1]=1000){this.resized=true;this.style.width=1000;}" align=absMiddle border=0>www.njcatv.net"
    autourl[2]="javacool.3322.net"
    autourl[3]=1000){this.resized=true;this.style.width=1000;}" align=absMiddle border=0>www.sina.com.cn"
    autourl[4]="www.nuaa.edu.cn"
    autourl[5]=1000){this.resized=true;this.style.width=1000;}" align=absMiddle border=0>www.cctv.com"
    function butt(){
    ***("<form name=autof>")
    for(var i=1;i<autourl.length;i++)
    ***("<input type=text name=txt"+i+" size=10 value="/测试中……> =》<input type=text
    name=url"+i+" size=40> =》<input type=button value="/blog/GO
    onclick=window.open(this.form.url"+i+".value)><br>")
    ***("<input type=submit value=刷新></form>")
    }
    butt()
    function auto(url){
    document.forms[0]["url"+b].value=url
    if(tim>200)
    {document.forms[0]["txt"+b].value="/链接超时"}
    else
    {document.forms[0]["txt"+b].value="/blog/时间"+tim/10+"秒"}
    b++
    }
    function run(){for(var i=1;i<autourl.length;i++)***("<img src=http://"+autourl+"/"+Math.random()+" width=1 height=1
    onerror=auto("http://"+autourl+"")>")}
    run()</script>


    37. 各种样式的光标
    auto :标准光标
    default :标准箭头
    hand :手形光标
    wait :等待光标
    text :I形光标
    vertical-text :水平I形光标
    no-drop :不可拖动光标
    not-allowed :无效光标
    help :?帮助光标
    all-scroll :三角方向标
    move :移动标
    crosshair :十字标
    e-resize
    n-resize
    nw-resize
    w-resize
    s-resize
    se-resize
    sw-resize


    38.页面进入和退出的特效
    进入页面<meta http-equiv="Page-Enter" content="revealTrans(duration=x, transition=y)">
    推出页面<meta http-equiv="Page-Exit" content="revealTrans(duration=x, transition=y)">  
    这个是页面被载入和调出时的一些特效。duration表示特效的持续时间,以秒为单位。transition表示使用哪种特效,取值为1-23:
      0 矩形缩小
      1 矩形扩大
      2 圆形缩小
      3 圆形扩大
      4 下到上刷新
      5 上到下刷新
      6 左到右刷新
      7 右到左刷新
      8 竖百叶窗
      9 横百叶窗
      10 错位横百叶窗
      11 错位竖百叶窗
      12 点扩散
      13 左右到中间刷新
      14 中间到左右刷新
      15 中间到上下
      16 上下到中间
      17 右下到左上
      18 右上到左下
      19 左上到右下
      20 左下到右上
      21 横条
      22 竖条
      23 以上22种随机选择一种


    39.在规定时间内跳转
    <META http-equiv=V="REFRESH" content="5;URL=http://www.51js.com">


    40.网页是否被检索
    <meta name="ROBOTS" content="属性值">
      其中属性值有以下一些:
      属性值为"all": 文件将被检索,且页上链接可被查询;
      属性值为"none": 文件不被检索,而且不查询页上的链接;
      属性值为"index": 文件将被检索;
      属性值为"follow": 查询页上的链接;
      属性值为"noindex": 文件不检索,但可被查询链接;
      属性值为"nofollow": 文件不被检索,但可查询页上的链接。


    41、email地址的分割
    把如下代码加入<body>区域中
    <a href="mailto:webmaster@sina.com">webmaster@sina.com</a>


    42、流动边框效果的表格
    把如下代码加入<body>区域中
    <SCRIPT>
    l=Array(6,7,8,9,'a','b','b','c','d','e','f')
    Nx=5;Ny=35
    t="<table border=0 cellspacing=0 cellpadding=0 height="+((Nx+2)*16)+"><tr>"
    for(x=Nx;x<Nx+Ny;x++)
    t+="<td width=16 id=a_mo"+x+"> </td>"
    t+="</tr><tr><td width=10 id=a_mo"+(Nx-1)+"> </td><td colspan="+(Ny-2)+" rowspan="+(Nx)+"> </td><td width=16 id=a_mo"+(Nx+Ny)+"></td></tr>"
    for(x=2;x<=Nx;x++)
    t+="<tr><td width=16 id=a_mo"+(Nx-x)+"> </td><td width=16 id=a_mo"+(Ny+Nx+x-1)+"> </td></tr>"
    t+="<tr>"
    for(x=Ny;x>0;x--)
    t+="<td width=16 id=a_mo"+(x+Nx*2+Ny-1)+"> </td>"
    ***(t+"</tr></table>")
    var N=Nx*2+Ny*2
    function f1(y){
    for(i=0;i<N;i++){
    c=(i+y)%20;if(c>10)c=20-c
    document.all["a_mo"+(i)].bgColor=""""#0000"+l[c]+l[c]+"'"}
    y++
    setTimeout('f1('+y+')','1')}
    f1(1)
    </SCRIPT>


    43、JavaScript主页弹出窗口技巧
    窗口中间弹出
    <script>
    window.open("http://www.cctv.com","","width=400,height=240,top="+(screen.availHeight-240)/2+",left="+(screen.availWidth-400)/2);
    </script>
    ============
    <html>
    <head>
    <script language="LiveScript">
    function WinOpen() {
        msg=open("","DisplayWindow","toolbar=no,directories=no,menubar=no");
        msg.***("<HEAD><TITLE>哈 罗!</TITLE></HEAD>");
        msg.***("<CENTER><H1>酷 毙 了!</H1><h2>这 是<B>JavaScript</B>所 开 的 视 窗!</h2></CENTER>");
    }
    </script>
    </head>
    <body>
    <form>
    <input type="button" name="Button1" value="Push me" onclick="WinOpen()">
    </form>
    </body>
    </html>
    ==============
    一、在下面的代码中,你只要单击打开一个窗口,即可链接到赛迪网。而当你想关闭时,只要单击一下即可关闭刚才打开的窗口。
      代码如下:
      <SCRIPT language="JavaScript">
      <!--
      function openclk() {
      another=open('1000){this.resized=true;this.style.width=1000;}" align=absMiddle border=0>http://www.ccidnet.com','NewWindow');
      }
      function closeclk() {
      another.close();
      }
      //-->
      </SCRIPT>
      <FORM>
      <INPUT TYPE="BUTTON" NAME="open" value="/打开一个窗口" onClick="openclk()">
      <BR>
      <INPUT TYPE="BUTTON" NAME="close" value="/blog/关闭这个窗口" onClick="closeclk()">
      </FORM>
      二、上面的代码也太静了,为何不来点动感呢?如果能给页面来个降落效果那该多好啊!
      代码如下:
      <script>
      function drop(n) {
      if(self.moveBy){
      self.moveBy (0,-900);
      for(i = n; i > 0; i--){
      self.moveBy(0,3);
      }
      for(j = 8; j > 0; j--){
      self.moveBy(0,j);
      self.moveBy(j,0);
      self.moveBy(0,-j);
      self.moveBy(-j,0);
      }
      }
      }
      </script>
      <body onLoad="drop(300)">
      三、讨厌很多网站总是按照默认窗口打开,如果你能随心所欲控制打开的窗口那该多好。
      代码如下:
      <SCRIPT LANGUAGE="JavaScript">
      <!-- Begin
      function popupPage(l, t, w, h) {
      var windowprops = "location=no,scrollbars=no,menubars=no,toolbars=no,resizable=yes" +
      ",left=" + l + ",top=" + t + ",width=" + w + ",height=" + h;
      var URL = "http://www.80cn.com";
      popup = window.open(URL,"MenuPopup",windowprops);
      }
      // End -->
      </script>
      <table>
      <tr>
      <td>
      <form name=popupform>
      <pre>
      打开页面的参数<br>
      离开左边的距离: <input type=text name=left size=2 maxlength=4> pixels
      离开右边的距离: <input type=text name=top size=2 maxlength=4> pixels
      窗口的宽度: <input type=text name=width size=2 maxlength=4> pixels
      窗口的高度: <input type=text name=height size=2 maxlength=4> pixels
      </pre>
      <center>
      <input type=button value="打开这个窗口!" onClick="popupPage(this.form.left.value, this.form.top.value, this.form.width.value,
    this.form.height.value)">
      </center>
      </form>
      </td>
      </tr>
      </table>你只要在相对应的对话框中输入一个数值即可,将要打开的页面的窗口控制得很好。


    44、页面的打开移动
    把如下代码加入<body>区域中
    <SCRIPT LANGUAGE="JavaScript">
    <!-- Begin
    for (t = 2; t > 0; t--) {
    for (x = 20; x > 0; x--) {
    for (y = 10; y > 0; y--) {
    parent.moveBy(0,-x);
       }
    }
    for (x = 20; x > 0; x--) {
    for (y = 10; y > 0; y--) {
    parent.moveBy(0,x);
       }
    }
    for (x = 20; x > 0; x--) {
    for (y = 10; y > 0; y--) {
    parent.moveBy(x,0);
       }
    }
    for (x = 20; x > 0; x--) {
    for (y = 10; y > 0; y--) {
    parent.moveBy(-x,0);
         }
       }
    }
    //-->
    //   End -->
    </script>


    45、显示个人客户端机器的日期和时间
    <script language="LiveScript">
    <!-- Hiding
       today = new Date()
       ***("现 在 时 间 是: ",today.getHours(),":",today.getMinutes())
       ***("<br>今 天 日 期 为: ", today.getMonth()+1,"/",today.getDate(),"/",today.getYear());
    // end hiding contents -->
    </script>


    46、自动的为你每次产生最後修改的日期了:
    <html>
    <body>
    This is a simple HTML- page.
    <br>
    Last changes:
       <script language="LiveScript">
       <!--   hide script from old browsers
         ***(document.lastModified)
       // end hiding contents -->
       </script>
    </body>
    </html>


    47、不能为空和邮件地址的约束:
    <html>
    <head>
    <script language="JavaScript">
    <!-- Hide
    function test1(form) {
       if (form.text1.value == "")
         alert("您 没 写 上 任 何 东 西, 请 再 输 入 一 次 !")
       else {
        alert("嗨 "+form.text1.value+"! 您 已 输 入 完 成 !");
       }
    }
    function test2(form) {
       if (form.text2.value == "" ||
           form.text2.value.indexOf('@', 0) == -1)
             alert("这 不 是 正 确 的 e-mail address! 请 再 输 入 一 次 !");
       else alert("您 已 输 入 完 成 !");
    }
    // -->
    </script>
    </head>
    <body>
    <form name="first">
    Enter your name:<br>
    <input type="text" name="text1">
    <input type="button" name="button1" value="输 入 测 试" onClick="test1(this.form)">
    <P>
    Enter your e-mail address:<br>
    <input type="text" name="text2">
    <input type="button" name="button2" value="输 入 测 试" onClick="test2(this.form)">
    </body>


    48、跑马灯
    <html>
    <head>
    <script language="JavaScript">
    <!-- Hide
    var scrtxt="怎麽样 ! 很酷吧 ! 您也可以试试."+"Here goes your message the visitors to your
    page will "+"look at for hours in pure fascination...";
    var lentxt=scrtxt.length;
    var width=100;
    var pos=1-width;
    function scroll() {
       pos++;
       var scroller="";
       if (pos==lentxt) {
         pos=1-width;
       }
       if (pos<0) {
         for (var i=1; i<=Math.abs(pos); i++) {
           scroller=scroller+" ";}
         scroller=scroller+scrtxt.substring(0,width-i+1);
       }
       else {
         scroller=scroller+scrtxt.substring(pos,width+pos);
       }
       window.status = scroller;
       setTimeout("scroll()",150);
       }
    //-->
    </script>
    </head>
    <body onLoad="scroll();return true;">
    这里可显示您的网页 !
    </body>
    </html>


    49、在网页中用按钮来控制前页,后页和主页的显示。
    <html>
    <body>
    <FORM NAME="buttonbar">
          <INPUT TYPE="button" VALUE="Back" onClick="history.back()">
          <INPUT TYPE="button" VALUE="JS- Home" onClick="location='script.html'">
          <INPUT TYPE="button" VALUE="Next" onCLick="history.forward()">
    </FORM>
    </body>
    </html>
    50、查看某网址的源代码
    把如下代码加入<body>区域中
    <SCRIPT>
    function add()
    {
    var ress=document.forms[0].luxiaoqing.value
    window.location="view-source:"+ress;
    }
    </SCRIPT>
    输入要查看源代码的URL地址:
    <FORM><input type="text" name="luxiaoqing" size=40 value="http://"></FORM>
    <FORM><br>
    <INPUT type="button" value="查看源代码" onClick=add()>
    </FORM>


    51、title显示日期
    把如下代码加入<body>区域中:
    <script language="JavaScript1.2">
    <!--hide
    var isnMonth = new
    Array("1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月");
    var isnDay = new
    Array("星期日","星期一","星期二","星期三","星期四","星期五","星期六","星期日");
    today = new Date () ;
    Year=today.getYear();
    Date=today.getDate();
    if (document.all)
    document.title="今天是: "+Year+"年"+isnMonth[today.getMonth()]+Date+"日"+isnDay[today.getDay()]
    //--hide-->
    </script>


    52、显示所有链接
    把如下代码加入<body>区域中
    <script language="JavaScript1.2">
    <!--
    function extractlinks(){
    var links=document.all.tags("A")
    var total=links.length
    var win2=window.open("","","menubar,scrollbars,toolbar")
    win2.***("<font size='2'>一共有"+total+"个连接</font><br>")
    for (i=0;i<total;i++){
    win2.***("<font size='2'>"+links[i].outerHTML+"</font><br>")
    }
    }
    //-->
    </script>
    <input type="button" onClick="extractlinks()" value="显示所有的连接">


    53、回车键换行
    把如下代码加入<body>区域中
    <script type="text/javascript">                
    function handleEnter (field, event) {
       var keyCode = event.keyCode ? event.keyCode : event.which ?
    event.which : event.charCode;
       if (keyCode == 13) {
        var i;
        for (i = 0; i < field.form.elements.length; i++)
         if (field == field.form.elements[i])
          break;
        i = (i + 1) % field.form.elements.length;
        field.form.elements[i].focus();
        return false;
       }
       else
       return true;
    }      
    </script>
    <form>
    <input type="text" onkeypress="return handleEnter(this, event)"><br>
    <input type="text" onkeypress="return handleEnter(this, event)"><br>
    <textarea>回车换行


    54、确认后提交
    把如下代码加入<body>区域中
    <SCRIPT LANGUAGE="JavaScript">
    <!--
    function msg(){
    if (confirm("你确认要提交嘛!"))
    document.lnman.submit()
    }
    //-->
    </SCRIPT>
    <form name="lnman" method="post" action="">
       <p>
         <input type="text" name="textfield" value="确认后提交">
       </p>
       <p>
         <input type="button" name="Submit" value="提交" onclick="msg();">
       </p>
    </form>


    55、改变表格的内容
    把如下代码加入<body>区域中
    <script ***script>
    var arr=new Array()
    arr[0]="一一一一一";
    arr[1]="二二二二二";
    arr[2]="三三三三三";
    </script>
    <select onchange="zz.cells[this.selectedIndex].innerHTML=arr[this.selectedIndex]">
       <option value=a>改变第一格</option>
       <option value=a>改变第二格</option>
       <option value=a>改变第三格</option>
    </select>
    <table id=zz border=1>
       <tr height=20>
         <td width=150>第一格</td>
    <td width=150>第二格</td>
    <td width=150>第三格</td>
       </tr>
    </table>

    posted @ 2008-10-09 20:10 鸿雁 阅读(197) | 评论 (0)编辑 收藏

    struts+spring+hibernate的web应用示例

         摘要: struts+spring+hibernate的web应用<一> 架构搭建 开发工具: Eclipse 3.2 + NLpack1-eclipse-SDK-3.2.1-win32( 中文语言包 ) 插件: tomcatPluginV31 :用于 tomcat 项目建立和管理。 Properties Editor : struts 中的资源国际化的中文编辑插件,不需要...  阅读全文

    posted @ 2008-03-01 21:39 鸿雁 阅读(819) | 评论 (0)编辑 收藏

    如何查找、删除表中重复的记录

    软件环境:

    1Windows NT4.0+ORACLE 8.0.4

    2ORACLE安装路径为:C:"ORANT

    问题提出:

    1、当我们想要为一个表创建唯一索引时,如果该表有重复的记录,则无法创建成功。

    方法原理:

    1Oracle中,每一条记录都有一个rowidrowid在整个数据库中是唯一的,

      rowid确定了每条记录是在ORACLE中的哪一个数据文件、块、行上。

    2、在重复的记录中,可能所有列的内容都相同,但rowid不会相同,所以只要确定出重复记录中

      那些具有最大rowid的就可以了,其余全部删除。

    3、以下语句用到了3项技巧:rowid、子查询、别名。

    实现方法:

    SQL> create table a (

     2 bm char(4),            --编码

     3 mc varchar2(20)            --名称

     4 )

     5 /

    表已建立.

    SQL> insert into a values('1111','1111');

    SQL> insert into a values('1112','1111');

    SQL> insert into a values('1113','1111');

    SQL> insert into a values('1114','1111');

    SQL> insert into a select * from a;

    插入4个记录.

    SQL> commit;

    完全提交.

    SQL> select rowid,bm,mc from a;

    ROWID              BM   MC

    ------------------ ---- -------

    000000D5.0000.0002 1111 1111

    000000D5.0001.0002 1112 1111

    000000D5.0002.0002 1113 1111

    000000D5.0003.0002 1114 1111

    000000D5.0004.0002 1111 1111

    000000D5.0005.0002 1112 1111

    000000D5.0006.0002 1113 1111

    000000D5.0007.0002 1114 1111

     

    查询到8记录.

    查出重复记录

    SQL> select rowid,bm,mc from a where a.rowid!=(select max(rowid) from a b where a.bm=b.bm and a.mc=b.mc);

    ROWID              BM   MC

    ------------------ ---- --------------------

    000000D5.0000.0002 1111 1111

    000000D5.0001.0002 1112 1111

    000000D5.0002.0002 1113 1111

    000000D5.0003.0002 1114 1111

     

    删除重复记录

    SQL> delete from a a where a.rowid!=(select max(rowid) from a b where a.bm=b.bm and a.mc=b.mc);

    删除4个记录.

    SQL> select rowid,bm,mc from a;

    ROWID              BM   MC

    ------------------ ---- --------------------

    000000D5.0004.0002 1111 1111

    000000D5.0005.0002 1112 1111

    000000D5.0006.0002 1113 1111

    000000D5.0007.0002 1114 1111

     

    come from :: http://dev.csdn.net/article/59/59333.shtm

    -测试数据

    /*-----------------------------

    select * from tt

    -----------------------------*/

    id          pid        

    ----------- -----------

    1           1

    1           1

    2           2

    3           3

    3           3

    3           3

    (所影响的行数为 6 行)

    首先,如何查询table中有重复记录

    select *,count(1) as rownum

    from tt

    group by id, pid

    having count(1) > 1

    id          pid         rownum     

    ----------- ----------- -----------

    1           1           2

    3           3           3

     

    (所影响的行数为 2 行)

    方法一:使用distinct和临时表

    if object_id('tempdb..#tmp') is not null

    drop table #tmp

    select distinct * into #tmp from tt

    truncate table tt

    insert into tt select * from #tmp

    方法二:添加标识列

    alter table tt add NewID int identity(1,1)

    go 

    delete from tt where exists(select 1 from tt a where a.newid>tt.newid and tt.id=a.id and tt.pid=a.pid)

    go

    alter table tt drop column NewID

    go

    --测试结果

    /*-----------------------------

    select * from tt

    -----------------------------*/

    id          pid        

    ----------- -----------

    1           1

    2           2

    3           3

    (所影响的行数为 3 行)

    *---*-- * 8 8 * * * * 8* * * * 8 8 *

    USE CEO

    CREATE TABLE TT

    (

    TTNO CHAR(4),

    TTNAME VARCHAR(10)

    )

    INSERT INTO TT (TTNO,TTNAME) VALUES ('1425','WHERE')

    INSERT INTO TT (TTNO,TTNAME) VALUES ('1425','WHERE')

    INSERT INTO TT (TTNO,TTNAME) VALUES ('1424','WHEREIS')

    INSERT INTO TT (TTNO,TTNAME) VALUES ('1435','WHEREIS')

    INSERT INTO TT (TTNO,TTNAME) VALUES ('1435','WHEREIS')

    方法二:添加标识列(最有效方法)

    alter table tt add newid2 int identity(1,1)

    go

    delete from tt where exists( select 1 from tt   a where a.newid2>tt.newid2 and tt.ttno=a.ttno and tt.ttname=a.ttname)

    alter table tt drop column newid2

    go

    select * from tt

    posted @ 2008-01-22 01:57 鸿雁 阅读(278) | 评论 (0)编辑 收藏

    Java程序员:一刻钟精通正则表达式

    开篇,还是得说说 ^ 和 $ 他们是分别用来匹配字符串的开始和结束,以下分别举例说明:

      "^The": 开头一定要有"The"字符串;

      "of despair$": 结尾一定要有"of despair" 的字符串;

      那么,

      "^abc$": 就是要求以abc开头和以abc结尾的字符串,实际上是只有abc匹配。

      "notice": 匹配包含notice的字符串。

      你可以看见如果你没有用我们提到的两个字符(最后一个例子),就是说 模式(正则表达式) 可以出现在被检验字符串的任何地方,你没有把他锁定到两边。

      接着,说说 '*', '+',和 '?',

      他们用来表示一个字符可以出现的次数或者顺序. 他们分别表示:

    1.   "zero or more"相当于{0,},
    2.   "one or more"相当于{1,},
    3.   "zero or one."相当于{0,1}, 这里是一些例子:
    4.   "ab*": 和ab{0,}同义,匹配以a开头,后面可以接0个或者N个b组成的字符串("a", "ab", "abbb", 等);
    5.   "ab+": 和ab{1,}同义,同上条一样,但最少要有一个b存在 ("ab", "abbb", 等.);
    6.   "ab?":和ab{0,1}同义,可以没有或者只有一个b;
    7.   "a?b+$": 匹配以一个或者0个a再加上一个以上的b结尾的字符串.

      要点, '*', '+',和 '?'只管它前面那个字符.

      你也可以在大括号里面限制字符出现的个数,比如

    1.   "ab{2}": 要求a后面一定要跟两个b(一个也不能少)("abb");
    2.   "ab{2,}": 要求a后面一定要有两个或者两个以上b(如"abb", "abbbb", 等.);
    3.   "ab{3,5}": 要求a后面可以有2-5个b("abbb", "abbbb", or "abbbbb").

      现在我们把一定几个字符放到小括号里,比如:

    1.   "a(bc)*": 匹配 a 后面跟0个或者一个"bc";
    2.   "a(bc){1,5}": 一个到5个 "bc."

      还有一个字符 '│', 相当于OR 操作:

      "hi│hello": 匹配含有"hi" 或者 "hello" 的 字符串;

      "(b│cd)ef": 匹配含有 "bef" 或者 "cdef"的字符串;

      "(a│b)*c": 匹配含有这样多个(包括0个)a或b,后面跟一个c的字符串;

      一个点('.')可以代表所有的单一字符,不包括"\n"

      如果,要匹配包括"\n"在内的所有单个字符,怎么办?

      对了,用'[\n.]'这种模式.

      "a.[0-9]": 一个a加一个字符再加一个0到9的数字

      "^.{3}$": 三个任意字符结尾 .

      中括号括住的内容只匹配一个单一的字符

      "[ab]": 匹配单个的 a 或者 b ( 和 "a│b" 一样);

      "[a-d]": 匹配'a' 到'd'的单个字符 (和"a│b│c│d" 还有 "[abcd]"效果一样); 一般我们都用[a-zA-Z]来指定字符为一个大小写英文

      "^[a-zA-Z]": 匹配以大小写字母开头的字符串

      "[0-9]%": 匹配含有 形如 x% 的字符串

      ",[a-zA-Z0-9]$": 匹配以逗号再加一个数字或字母结尾的字符串

      你也可以把你不想要得字符列在中括号里,你只需要在总括号里面使用'^' 作为开头 "%[^a-zA-Z]%" 匹配含有两个百分号里面有一个非字母的字符串.

      要点:^用在中括号开头的时候,就表示排除括号里的字符。为了PHP能够解释,你必须在这些字符面前后加'',并且将一些字符转义.

      不要忘记在中括号里面的字符是这条规路的例外?在中括号里面, 所有的特殊字符,包括(''), 都将失去他们的特殊性质 "[*\+?{}.]"匹配含有这些字符的字符串.

      还有,正如regx的手册告诉我们: "如果列表里含有 ']', 最好把它作为列表里的第一个字符(可能跟在'^'后面). 如果含有'-', 最好把它放在最前面或者最后面, or 或者一个范围的第二个结束点[a-d-0-9]中间的‘-’将有效.

      看了上面的例子,你对{n,m}应该理解了吧.要注意的是,n和m都不能为负整数,而且n总是小于m. 这样,才能 最少匹配n次且最多匹配m次. 如"p{1,5}"将匹配 "pvpppppp"中的前五个p.

    下面说说以\开头的

      \b 书上说他是用来匹配一个单词边界,就是...比如've\b',可以匹配love里的ve而不匹配very里有ve

      \B 正好和上面的\b相反.例子我就不举了

      .....突然想起来....可以到http://www.phpv.net/article.php/251 看看其它用\ 开头的语法

      好,我们来做个应用:

      如何构建一个模式来匹配 货币数量 的输入

      构建一个匹配模式去检查输入的信息是否为一个表示money的数字。我们认为一个表示money的数量有四种方式: "10000.00" 和 "10,000.00",或者没有小数部分, "10000" and "10,000". 现在让我们开始构建这个匹配模式:

    ^[1-9][0-9]*$

    这是所变量必须以非0的数字开头.但这也意味着 单一的 "0" 也不能通过测试. 以下是解决的方法:

    ^(0│[1-9][0-9]*)$

      "只有0和不以0开头的数字与之匹配",我们也可以允许一个负号在数字之前:

    ^(0│-?[1-9][0-9]*)$

      这就是: "0 或者 一个以0开头 且可能 有一个负号在前面的数字." 好了,现在让我们别那么严谨,允许以0开头.现在让我们放弃 负号 , 因为我们在表示钱币的时候并不需要用到. 我们现在指定 模式 用来匹配小数部分:

    ^[0-9]+(\.[0-9]+)?$

      这暗示匹配的字符串必须最少以一个阿拉伯数字开头. 但是注意,在上面模式中 "10." 是不匹配的, 只有 "10" 和 "10.2" 才可以. (你知道为什么吗)

    ^[0-9]+(\.[0-9]{2})?$

      我们上面指定小数点后面必须有两位小数.如果你认为这样太苛刻,你可以改成:

    ^[0-9]+(\.[0-9]{1,2})?$

      这将允许小数点后面有一到两个字符. 现在我们加上用来增加可读性的逗号(每隔三位), 我们可以这样表示:

    ^[0-9]{1,3}(,[0-9]{3})*(\.[0-9]{1,2})?$

      不要忘记 '+' 可以被 '*' 替代 如果你想允许空白字符串被输入话 (为什么?). 也不要忘记反斜杆 ’\’ 在php字符串中可能会出现错误 (很普遍的错误).

      现在,我们已经可以确认字符串了, 我们现在把所有逗号都去掉 str_replace(",", "", $money) 然后在把类型看成 double然后我们就可以通过他做数学计算了.

    再来一个:

      构造检查email的正则表达式

      在一个完整的email地址中有三个部分:

      1. 用户名 (在 '@' 左边的一切),

      2.'@',

      3. 服务器名(就是剩下那部分).

      用户名可以含有大小写字母阿拉伯数字,句号 ('.'), 减号('-'), and 下划线 ('_'). 服务器名字也是符合这个规则,当然下划线除外.

      现在, 用户名的开始和结束都不能是句点. 服务器也是这样. 还有你不能有两个连续的句点他们之间至少存在一个字符,好现在我们来看一下怎么为用户名写一个匹配模式:

    ^[_a-zA-Z0-9-]+$

      现在还不能允许句号的存在. 我们把它加上:

    ^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*$

      上面的意思就是说: "以至少一个规范字符(除了.)开头,后面跟着0个或者多个以点开始的字符串."

      简单化一点, 我们可以用 eregi()取代 ereg().eregi()对大小写不敏感, 我们就不需要指定两个范围 "a-z" 和 "A-Z" ? 只需要指定一个就可以了:

    ^[_a-z0-9-]+(\.[_a-z0-9-]+)*$

      后面的服务器名字也是一样,但要去掉下划线:

    ^[a-z0-9-]+(\.[a-z0-9-]+)*$

      好. 现在只需要用”@”把两部分连接:

    ^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*$

      这就是完整的email认证匹配模式了,只需要调用

    eregi(‘^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*$ ’,$eamil)

      就可以得到是否为email了.


    正则表达式的其他用法

      提取字符串

      ereg() and eregi() 有一个特性是允许用户通过正则表达式去提取字符串的一部分(具体用法你可以阅读手册). 比如说,我们想从 path/URL 提取文件名 ? 下面的代码就是你需要:

    ereg("([^\\/]*)$", $pathOrUrl, $regs);
    echo $regs[1];

      高级的代换

      ereg_replace() 和 eregi_replace()也是非常有用的: 假如我们想把所有的间隔负号都替换成逗号:

    ereg_replace("[ \n\r\t]+", ",", trim($str));

      最后,我把另一串检查EMAIL的正则表达式让看文章的你来分析一下.

    "^[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+'.'@'.'[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.'.'[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$"

      如果能方便的读懂,那这篇文章的目的就达到了.

    posted @ 2008-01-16 15:59 鸿雁 阅读(282) | 评论 (0)编辑 收藏

    Hibernate3调用存储过程用法

    DB2中简单存储过程 selectAllUsers

    CREATE procedure selectAllUsers
    DYNAMIC RESULT SETS 1
    BEGIN
    DECLARE temp_cursor1 CURSOR WITH RETURN TO CLIENT FOR
    SELECT * FROM test;
    OPEN temp_cursor1;
    END;

    映射文件中关于存储过程内容如下

    <hibernate-mapping package="com.unmi.vo">
    <class name="Test" table="TEST">
         ............
    </class>

    <sql-query callable="true" name="selectAllUsers">
    <return alias="aa" class="Test">
    <return-property name="oborqt" column="OBORQT"/>
    <return-property name="moorqt" column="MOORQT"/>
    <return-property name="roschn" column="ROSCHN"/>
    <return-property name="plandate" column="PLANDATE"/>
    </return>
    { ? = call selectAllUsers() }
    </sql-query>
    </hibernate-mapping>

    { ? = call selectAllUsers() } 也可以写成{ call selectAllUsers() },
    如果有参数就写成
    { ? = call selectAllUsers(?,?,?) }
    代码中对query设置相应位置上的值就OK

    Java调用关键代码如下

    Session session = HibernateUtil.currentSession();
    Query query = session.getNamedQuery("selectAllUsers");
    List list = query.list();
    System.out.println(list);

    要求你的存储过程必须能返回记录集,否则要出错
    如果你的存储过程是完成非查询任务就应该在配置文件用以下三个标签
    <sql-insert callable="true">{call createPerson (?, ?)}</sql-insert>
    <sql-delete callable="true">{? = call deletePerson (?)}</sql-delete>
    <sql-update callable="true">{? = call updatePerson (?, ?)}</sql-update>

    posted @ 2008-01-11 18:31 鸿雁 阅读(263) | 评论 (0)编辑 收藏

    数字智力测试题

    ——此文章摘自《Java程序员面试宝典》定价:46元 特价:38.9元 购买>>

        回答智力测试的一些基本方法如下。
        (1)排除法
        把一些无关的问题先予以排除,可以确定的问题先确定,尽可能缩小未知的范围,以便于问题的分析和解决。这种思维方式在我们的工作和生活中都是很有用处的。

        (2)递推法
        由已知条件层层向下分析,要确保每一步都能准确无误。可能会有几个分支,应本着先易后难的原则,先从简单的一支入手。

        (3)倒推法
        从问题最后的结果开始,一步一步往前推,直到求出问题的答案。有些问题用此法解起来很简单,如用其他方法则很难。

        (4)假设法
        对给定的问题,先做一个或一些假设,然后根据已给的条件进行分析,如果出现与题目给的条件有矛盾的情况,说明假设错误,可再做另一个或另一些假设。如果结果只有两种可能,那么问题就已经解决了。在科学史上,“假设”曾起了极大的作用。

        (5)计算法
        有些问题必须经计算才能解决。要注意的是,智力测验中的问题往往含有隐含的条件,有时给出的数是无用的。

        (6)分析法
        这是最基本的方法。各种方法常常要用到分析法。可以说,分析能力的高低,是一个人的智力水平的体现。分析能力不仅是先天性的,在很大程度上取决于后天的训练,应养成对客观事物进行分析的良好习惯。

        (7)作图法
        根据问题中已知的条件,采用适当的方法画出图形,有助于问题的解决。有些问题,在没画图之前,会觉得无处下手,画了图后就一目了然了。

        (8)综合法
        事实上,许多问题都要运用几种不同的方法才能解决。所谓综合法,就是综合各种方法(包括前述各种方法以外的方法)去解决某些问题。

        面试例题1:100美元哪里去了?

        3个朋友住进了一家宾馆。结账时,账单总计3 000美元。3个朋友每人分摊1 000美元,并把这3 000美元如数交给了服务员,委托他代到总台交账。但在交账时,正逢宾馆实施价格优惠,总台退还给服务员500美元,实收2 500美元。服务员从这500美元退款中扣下了200美元,只退还3个客人300美元。3个客人平分了这300美元,每人取回了100美元。这样,3个客人每人实际支付900美元,共支付2 700美元,加上服务员扣的200美元,共计2 900美元,那么这100美元的差额到哪里去了?

        答案:这道题纯粹是文字游戏,但是如果你的头脑不够清晰,很可能把你搞糊涂了。客人实际支付2 700美元,就等于总台实际结收的2 500美元加上服务员克扣的200美元。在这2 700美元上加上200美元是毫无道理的,而在这2 700美元上加退回的300美元,这是有道理的,因为这等于客人原先交给服务员的3 000美元。

        面试例题2:击鼠标比赛现在开始!参赛者有拉尔夫、威利和保罗。

        拉尔夫10秒钟能击10下鼠标,威利20秒钟能击20下鼠标,保罗5秒钟能击5下鼠标。以上各人所用的时间是这样计算的:从第一击开始,到最后一击结束。

        他们是否打平手?如果不是,谁最先击完40下鼠标?

        解析:n秒钟击n下鼠标其实是击第一下鼠标时才开始计时的,实际上击n-1下需要n秒钟,那么若击40下鼠标,拉尔夫需要(40-1)/(9/10)=39/0.9秒,威利需要(40-1)/(19/20)=39/0.95秒,保罗需要(40-1)/(4/5)=39/0.8秒,因此威利先击完。

        答案:威利先击完。

        面试例题3:父亲打电话给女儿,要她替自己买一些生活用品,同时告诉她,钱放在书桌上的一个信封里。女儿找到信封,看见上面写着98,以为信封内有98元,就把钱拿出来,数也没数放进书包里。在商店里,她买了90元的东西,付款时才发现,她不仅没有剩下8元,反而差了4元。回到家里,她把这事告诉了父亲,怀疑父亲把钱点错了。父亲笑着说,他并没有数错,错在女儿身上。

        问:女儿错在什么地方?

        答案:拿倒了,86看成是98了。

        面试例题4:3个孩子翻衣兜,他们把兜里所有的钱都掏出来,看看一共有多少钱。结果一共有320日元。其中有两枚硬币是100日元的,两枚是50日元的,两枚是10日元的。每一个孩子所带的硬币中没有相同的。而且,没带100日元硬币的孩子也没带10日元的硬币,没带50日元硬币的孩子也没带100日元的硬币。你能弄清楚这3个日本孩子原来各自带了什么硬币吗?

        答案:第一个小孩:100,50,10;第二个小孩:100,50;第三个小孩:10。

        面试例题5:有一种小虫,每隔2秒钟分裂一次。分裂后的2只新的小虫经过2秒钟后又会分裂。如果最初某瓶中只有一只小虫,那么2秒后变2只,再过2秒后就变4只……2分钟后,正好满满一瓶小虫。假设这个瓶内最初放入2只这样的小虫。

        问:经过多少时间后,正巧也是满满的一瓶?

        答案:经过1分58秒时间,也正巧是满满一瓶。因为从一只虫蜕变为2只虫只需2秒钟。在瓶内只有一只虫子的情况下,经过2秒钟后就变成2只。这时的情况和瓶内一开始就有2只虫子的情况是一样的。出现这两种情况的时间差是2秒钟。所以,经过1分58秒后,也正好是满满一瓶。

        面试例题6:斯芬克斯是古代希腊神话中的带翅膀的狮子女魔。传说她在底比斯附近要人猜谜,猜不出来就要杀人。一次,她要底比斯王子猜谜:“有一种动物,早上4条腿,中午2条腿,晚上3条腿,是什么动物?”聪明的王子说:“是人。”他猜中了。

        如果你是现代的斯芬克斯,会提出什么样的问题呢?比如,1和0之间加上什么符号才可以使得到的数比0大又比1小呢?你知道吗?

        答案:0.1

        面试例题7:你让工人为你工作7天,给工人的回报是一根金条。金条平分成相连的7段,你必须在每天结束时给他们一段金条,如果只许你两次把金条弄断,你如何给你的工人付费?你让工人为你工作7天,给工人的回报是一根金条。金条平分成相连的7段,你必须在每天结束时给他们一段金条,如果只许你两次把金条弄断,你如何给你的工人付费?

        答案:两次弄断就应分成三份,我把金条分成1/7、2/7和4/7三份。这样,第1天我就可以给他1/7;第2天我给他2/7,让他找回我1/7;第3天我就再给他1/7,加上原先的2/7就是3/7;第4天我给他那块4/7,让他找回那两块1/7和2/7的金条;第5天,再给他1/7;第6天和第2天一样;第7天给他找回的那个1/7。

         面试题8:对一批编号为1-100全部开关朝上(开)的灯进行以下操作:凡是1的倍数反方向拨一次开关;2的倍数反方向又拨一次开关;3的倍数反方向又拨一次开关。。。。。问:最后为关熄状态的灯的编号。    
       
      答案:  
      1,4,9,16,25,36,47,64,81,100关 

         面试题9:  
      小明和小强都是王老师的学生。王老师的生日是m月n日,2人都指导王老师的生日是下列10组中的一天,王老师把m值告诉了小明,把n值告诉了小强。王老师文他们知道他的生日是那一天吗?  
      3月4   日,3月5   日,3月9   日  
      6月   4日,6月   7日  
      9月   1日,9月   5日  
      12月   1日,12月   2日,12月   8日  
      小明说:我不知道的话,小强肯定也不知道  
      小强说:笨来我不知道,但是现在我知道了  
      小明说:哦,那我也知道了  
      情根据上面的对话推断出王老师的生日是哪一天?
      
       答案:9月1号准没错  
      首先小明是不知道的,所以不可能在某月的   2号,7号,8号,9号.去掉一些日子后;简化如下  
      3月4   日,3月5   日,  
      6月   4日,  
      9月   1日,9月   5日  
      12月   1日,  
      小强说他不知道,小明也不会知道.即小强知道的月数不会在有   2号,7号,8号,9号   的这些月数,因为如果在这些月数,小明是有可能知道的.最后只剩下   9月1日   和   9月5日.  
      这时候小明也清楚M是9月了,小强也因此知道N是1号.   


         神的交通工具是什么------神奇(骑)宝贝
      什么动物可以贴在墙上------海豹(报)
      什么颜色最会模仿------红(磨坊)模仿
      什么鸡最慢------尼可基(鸡)曼
      辣妹什么地方最香------腊梅处处香
      哪位古人跑得最快------曹操(说曹操曹操就到)
      什么动物没有方向感------麋鹿(迷路)
      茉莉花、太阳花、玫瑰花,哪一朵花最没力------茉莉花[好一朵美丽(没力)的茉莉花]
      猴子最付厌什么钱------平行线[没有相交(香蕉)]
      象皮、老虎皮、狮子つ囊桓霰冉喜?-----象皮擦(象皮差)
      木鱼掉进海里会变成什么------虱目鱼(湿木鱼)
      哪一位艺人讲的笑话最冷------蔡依琳(衣淋湿就冷)
      狼、老虎和狮子谁玩游戏一定会被淘汰------狼,桃太郎(淘汰狼)
      孔子有三位徒弟子贡、子路和子游,请问哪一位不是人------子路(指鹿为马)
      布跟纸怕什么------不怕一万只怕万一 (布怕一万纸怕万一)
      麒麟飞到北极会变成什么------冰淇淋(冰麒麟)
      哪个历史人物游泳必定沉下去------阿斗,扶(浮)不起的阿斗
      星星、月亮、太阳哪一个是哑巴------星星,歌中有「天上的星星不说话 」
      铅笔姓什么------萧,削(萧)铅笔
      糖果是公的还是母的------母的,因为它会生蚂蚁
      请问哪一种花没有孩子------五月花, 五月花卫生纸(未生子)
      左和右,谁喜欢独奏,谁又比较好------左solo, 右so good (左手锣,右手鼓)
      为什么蚕宝宝很有钱------蚕会结茧(节俭)
      周瑜与诸葛亮的母亲分别姓什么------[既]生瑜,[何]生亮
      和谁交往最辛苦------莉莉,粒粒(莉莉)皆辛苦
      蝴蝶、蚂蚁、蜘蛛、蜈蚣,哪一个没有领到酬劳------蜈蚣,无功(蜈蚣)不受禄
      哪位历史人物最欠扁------苏武,苏武牧羊北海边(被海扁)
      123456789哪个数字最勤劳,哪个数字最懒惰------1最勤劳2最懒惰(一不做二不休)
      谁家没有电话------天衣(天衣无缝 phone)
      怎样使麻雀安静下来------压它一下(鸦雀无声)
      哪一家的路最窄------冤家(冤家路窄)
      红豆的小孩是谁------南国(红豆生南国)
      有位妈妈生了连体婴,姐姐叫玛丽,那么妹妹叫什么------梦露,玛丽莲(连)梦露

    posted @ 2008-01-05 15:05 鸿雁 阅读(1674) | 评论 (0)编辑 收藏

    常用正则表达式


    正则表达式用于字符串处理、表单验证等场合,实用高效。现将一些常用的表达式收集于此,以备不时之需。

    匹配中文字符的正则表达式: [\u4e00-\u9fa5]
    评注:匹配中文还真是个头疼的事,有了这个表达式就好办了

    匹配双字节字符(包括汉字在内):[^\x00-\xff]
    评注:可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)

    匹配空白行的正则表达式:\n\s*\r
    评注:可以用来删除空白行

    匹配HTML标记的正则表达式:<(\S*?)[^>]*>.*?</\1>|<.*? />
    评注:网上流传的版本太糟糕,上面这个也仅仅能匹配部分,对于复杂的嵌套标记依旧无能为力

    匹配首尾空白字符的正则表达式:^\s*|\s*$
    评注:可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式

    匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
    评注:表单验证时很实用

    匹配网址URL的正则表达式:[a-zA-z]+://[^\s]*
    评注:网上流传的版本功能很有限,上面这个基本可以满足需求

    匹配帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
    评注:表单验证时很实用

    匹配国内电话号码:\d{3}-\d{8}|\d{4}-\d{7}
    评注:匹配形式如 0511-4405222 或 021-87888822

    匹配腾讯QQ号:[1-9][0-9]{4,}
    评注:腾讯QQ号从10000开始

    匹配中国邮政编码:[1-9]\d{5}(?!\d)
    评注:中国邮政编码为6位数字

    匹配身份证:\d{15}|\d{18}
    评注:中国的身份证为15位或18位

    匹配ip地址:\d+\.\d+\.\d+\.\d+
    评注:提取ip地址时有用

    匹配特定数字:
    ^[1-9]\d*$    //匹配正整数
    ^-[1-9]\d*$   //匹配负整数
    ^-?[1-9]\d*$   //匹配整数
    ^[1-9]\d*|0$  //匹配非负整数(正整数 + 0)
    ^-[1-9]\d*|0$   //匹配非正整数(负整数 + 0)
    ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$   //匹配正浮点数
    ^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$  //匹配负浮点数
    ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$  //匹配浮点数
    ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$   //匹配非负浮点数(正浮点数 + 0)
    ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$  //匹配非正浮点数(负浮点数 + 0)
    评注:处理大量数据时有用,具体应用时注意修正

    匹配特定字符串:
    ^[A-Za-z]+$  //匹配由26个英文字母组成的字符串
    ^[A-Z]+$  //匹配由26个英文字母的大写组成的字符串
    ^[a-z]+$  //匹配由26个英文字母的小写组成的字符串
    ^[A-Za-z0-9]+$  //匹配由数字和26个英文字母组成的字符串
    ^\w+$  //匹配由数字、26个英文字母或者下划线组成的字符串
    评注:最基本也是最常用的一些表达式

    原载地址:http://lifesinger.3322.org/myblog/?p=185

    posted @ 2007-12-24 00:02 鸿雁 阅读(382) | 评论 (0)编辑 收藏

    数据库和数据仓库的区别

    数据库是面向事务的设计,数据仓库是面向主题设计的。数据库一般存储在线交易数据,数据仓库存储的一般是历史数据。

    数据库设计是尽量避免冗余,一般采用符合范式的规则来设计,数据仓库在设计是有意引入冗余,采用反范式的方式来设计。

    数据库是为捕获数据而设计,数据仓库是为分析数据而设计,它的两个基本的元素是维表和事实表。(维是看问题的角度,比如时间,部门,维表放的就是这些东西的定义,事实表里放着要查询的数据,同时有维的ID)

    数据仓库,是在数据库已经大量存在的情况下,为了进一步挖掘数据资源、为了决策需要而产生的,它决不是所谓的“大型数据库”。那么,数据仓库与传统数据库比较,有哪些不同呢?让我们先看看W.H.Inmon关于数据仓库的定义:面向主题的、集成的、与时间相关且不可修改的数据集合。

    “面向主题的”:传统数据库主要是为应用程序进行数据处理,未必按照同一主题存储数据;数据仓库侧重于数据分析工作,是按照主题存储的。这一点,类似于传统农贸市场与超市的区别—市场里面,白菜、萝卜、香菜会在一个摊位上,如果它们是一个小贩卖的;而超市里,白菜、萝卜、香菜则各自一块。也就是说,市场里的菜(数据)是按照小贩(应用程序)归堆(存储)的,超市里面则是按照菜的类型(同主题)归堆的。

    “与时间相关”:数据库保存信息的时候,并不强调一定有时间信息。数据仓库则不同,出于决策的需要,数据仓库中的数据都要标明时间属性。决策中,时间属性很重要。同样都是累计购买过九车产品的顾客,一位是最近三个月购买九车,一位是最近一年从未买过,这对于决策者意义是不同的。

    “不可修改”:数据仓库中的数据并不是最新的,而是来源于其它数据源。数据仓库反映的是历史信息,并不是很多数据库处理的那种日常事务数据(有的数据库例如电信计费数据库甚至处理实时信息)。因此,数据仓库中的数据是极少或根本不修改的;当然,向数据仓库添加数据是允许的。

    数据仓库的出现,并不是要取代数据库。目前,大部分数据仓库还是用关系数据库管理系统来管理的。可以说,数据库、数据仓库相辅相成、各有千秋

    补充一下,数据仓库的方案建设的目的,是为前端查询和分析作为基础,由于有较大的冗余,所以需要的存储也较大。为了更好地为前端应用服务,数据仓库必须有如下几点优点,否则是失败的数据仓库方案。

    1.效率足够高。客户要求的分析数据一般分为日、周、月、季、年等,可以看出,日为周期的数据要求的效率最高,要求24小时甚至12小时内,客户能看到昨天的数据分析。由于有的企业每日的数据量很大,设计不好的数据仓库经常会出问题,延迟1-3日才能给出数据,显然不行的。

    2.数据质量。客户要看各种信息,肯定要准确的数据,但由于数据仓库流程至少分为3步,2次ETL,复杂的架构会更多层次,那么由于数据源有脏数据或者代码不严谨,都可以导致数据失真,客户看到错误的信息就可能导致分析出错误的决策,造成损失,而不是效益。

    3.扩展性。之所以有的大型数据仓库系统架构设计复杂,是因为考虑到了未来3-5年的扩展性,这样的话,客户不用太快花钱去重建数据仓库系统,就能很稳定运行。主要体现在数据建模的合理性,数据仓库方案中多出一些中间层,使海量数据流有足够的缓冲,不至于数据量大很多,就运行不起来了.

    posted @ 2007-12-05 00:07 鸿雁 阅读(183) | 评论 (0)编辑 收藏

    数据抽取、清洗与转换 BI项目中ETL设计

           ETL是将业务系统的数据经过抽取、清洗转换之后加载到数据仓库的过程,目的是将企业中的分散、零乱、标准不统一的数据整合到一起,为企业的决策提供分析的依据

      ETLBI项目最重要的一个环节,通常情况下ETL会花掉整个项目的1/3的时间,ETL设计的好坏直接关接到BI项目的成败。ETL也是一个长期的过程,只有不断的发现问题并解决问题,才能使ETL运行效率更高,为项目后期开发提供准确的数据。

      ETL的设计分三部分:数据抽取、数据的清洗转换、数据的加载。在设计ETL的时候也是从这三部分出发。数据的抽取是从各个不同的数据源抽取到ODS中(这个过程也可以做一些数据的清洗和转换),在抽取的过程中需要挑选不同的抽取方法,尽可能的提高ETL的运行效率。ETL三个部分中,花费时间最长的是T(清洗、转换)的部分,一般情况下这部分工作量是整个ETL的2/3。数据的加载一般在数据清洗完了之后直接写入DW中去。

      ETL的实现有多种方法,常用的有三种,第一种是借助ETL工具如Oracle的OWB、SQL server 2000的DTS、SQL Server2005的SSIS服务、informatic等实现,第二种是SQL方式实现,第三种是ETL工具和SQL相结合。前两种方法各有优缺点,借助工具可以快速的建立起ETL工程,屏蔽复杂的编码任务,提高速度,降低难度,但是欠缺灵活性。SQL的方法优点是灵活,提高ETL运行效率,但是编码复杂,对技术要求比较高。第三种是综合了前面二种的优点,极大的提高ETL的开发速度和效率。

      数据的抽取

      数据的抽取需要在调研阶段做大量工作,首先要搞清楚以下几个问题:数据是从几个业务系统中来?各个业务系统的数据库服务器运行什么DBMS?是否存在手工数据,手工数据量有多大?是否存在非结构化的数据?等等类似问题,当收集完这些信息之后才可以进行数据抽取的设计。

      1、与存放DW的数据库系统相同的数据源处理方法

      这一类数源在设计比较容易,一般情况下,DBMS(包括SQLServer,Oracle)都会提供数据库链接功能,在DW数据库服务器和原业务系统之间建立直接的链接关系就可以写Select 语句直接访问。

      2、与DW数据库系统不同的数据源的处理方法。

      这一类数据源一般情况下也可以通过ODBC的方式建立数据库链接,如SQL Server和Oracle之间。如果不能建立数据库链接,可以有两种方式完成,一种是通过工具将源数据导出成.txt或者是.xls文件,然后再将这些源系统文件导入到ODS中。另外一种方法通过程序接口来完成。

      3、对于文件类型数据源(.txt,,xls),可以培训业务人员利用数据库工具将这些数据导入到指定的数据库,然后从指定的数据库抽取。或者可以借助工具实现,如SQL SERVER 2005 的SSIS服务的平面数据源和平面目标等组件导入ODS中去。

      4、增量更新问题

      对于数据量大的系统,必须考虑增量抽取。一般情况,业务系统会记录业务发生的时间,可以用作增量的标志,每次抽取之前首先判断ODS中记录最大的时间,然后根据这个时间去业务系统取大于这个时间的所有记录。利用业务系统的时间戳,一般情况下,业务系统没有或者部分有时间戳。

      数据的清洗转换

      一般情况下,数据仓库分为ODS、DW两部分,通常的做法是从业务系统到ODS做清洗,将脏数据和不完整数据过滤掉,再从ODS到DW的过程中转换,进行一些业务规则的计算和聚合。

               1、数据清洗

      数据清洗的任务是过滤那些不符合要求的数据,将过滤的结果交给业务主管部门,确认是否过滤掉还是由业务单位修正之后再进行抽取。不符合要求的数据主要是有不完整的数据、错误的数据和重复的数据三大类。

    •   A、不完整的数据,其特征是是一些应该有的信息缺失,如供应商的名称,分公司的名称,客户的区域信息缺失、业务系统中主表与明细表不能匹配等。需要将这一类数据过滤出来,按缺失的内容分别写入不同Excel文件向客户提交,要求在规定的时间内补全。补全后才写入数据仓库。
    •   B、错误的数据,产生原因是业务系统不够健全,在接收输入后没有进行判断直接写入后台数据库造成的,比如数值数据输成全角数字字符、字符串数据后面有一个回车、日期格式不正确、日期越界等。这一类数据也要分类,对于类似于全角字符、数据前后有不面见字符的问题只能写SQL的方式找出来,然后要求客户在业务系统修正之后抽取;日期格式不正确的或者是日期越界的这一类错误会导致ETL运行失败,这一类错误需要去业务系统数据库SQL的方式挑出来,交给业务主管部门要求限期修正,修正之后再抽取。
    •   C、重复的数据,特别是维表中比较常见,将重复的数据的记录所有字段导出来,让客户确认并整理。

      数据清洗是一个反复的过程,不可能在几天内完成,只有不断的发现问题,解决问题。对于是否过滤、是否修正一般要求客户确认;对于过滤掉的数据,写入Excel文件或者将过滤数据写入数据表,在ETL开发的初期可以每天向业务单位发送过滤数据的邮件,促使他们尽快的修正错误,同时也可以作为将来验证数据的依据。数据清洗需要注意的是不要将有用的数据过滤掉了,对于每个过滤规则认真进行验证,并要用户确认才行。

      2、数据转换

      数据转换的任务主要是进行不一致的数据转换、数据粒度的转换和一些商务规则的计算。

    •   A、不一致数据转换,这个过程是一个整合的过程,将不同业务系统的相同类型的数据统一,比如同一个供应商在结算系统的编码是XX0001,而在CRM中编码是YY0001,这样在抽取过来之后统一转换成一个编码。
    •   B、数据粒度的转换,业务系统一般存储非常明细的数据,而数据仓库中的数据是用来分析的,不需要非常明细的数据,一般情况下,会将业务系统数据按照数据仓库粒度进行聚合。
    •   C、商务规则的计算,不同的企业有不同的业务规则,不同的数据指标,这些指标有的时候不是简单的加加减减就能完成,这个时候需要在ETL中将这些数据指标计算好了之后存储在数据仓库中,供分析使用。

      ETL日志与警告发送

      1、ETL日志,记录日志的目的是随时可以知道ETL运行情况,如果出错了,出错在那里。

      ETL日志分为三类。第一类是执行过程日志,是在ETL执行过程中每执行一步的记录,记录每次运行每一步骤的起始时间,影响了多少行数据,流水账形式。第二类是错误日志,当某个模块出错的时候需要写错误日志,记录每次出错的时间,出错的模块以及出错的信息等。第三类日志是总体日志,只记录ETL开始时间,结束时间是否成功信息。

      如果使用ETL工具,工具会自动产生一些日志,这一类日志也可以作为ETL日志的一部分。

      2、警告发送

      ETL出错了,不仅要写ETL出错日志而且要向系统管理员发送警告,发送警告的方式有多种,常用的就是给系统管理员发送邮件,并附上出错的信息,方便管理员排查错误。

    posted @ 2007-06-21 21:36 鸿雁 阅读(492) | 评论 (0)编辑 收藏

    海量数据库的查询优化及分页算法方案 (转载)

    海量数据库的查询优化及分页算法方案

    随着“金盾工程”建设的逐步深入和公安信息化的高速发展,公安计算机应用系统被广泛应用在各警种、各部门。与此同时,应用系统体系的核心、系统数据的存放地――数据库也随着实际应用而急剧膨胀,一些大规模的系统,如人口系统的数据甚至超过了1000万条,可谓海量。那么,如何实现快速地从这些超大容量的数据库中提取数据(查询)、分析、统计以及提取数据后进行数据分页已成为各地系统管理员和数据库管理员亟待解决的难题。

    在以下的文章中,我将以“办公自动化”系统为例,探讨如何在有着1000万条数据的MS SQL SERVER数据库中实现快速的数据提取和数据分页。以下代码说明了我们实例中数据库的“红头文件”一表的部分数据结构:

    CREATE TABLE [dbo].[TGongwen] ( --TGongwen是红头文件表名

    [Gid] [int] IDENTITY (1, 1) NOT NULL ,
    --本表的id号,也是主键

    [title] [varchar] (80) COLLATE Chinese_PRC_CI_AS NULL ,
    --红头文件的标题

    [fariqi] [datetime] NULL ,
    --发布日期

    [neibuYonghu] [varchar] (70) COLLATE Chinese_PRC_CI_AS NULL ,
    --发布用户

    [reader] [varchar] (900) COLLATE Chinese_PRC_CI_AS NULL ,

    --需要浏览的用户。每个用户中间用分隔符“,”分开

    ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

    GO

    下面,我们来往数据库中添加1000万条数据:

    declare @i int

    set @i=1

    while @i<=250000

    begin

    insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-2-5','通信科','通信科,办公室,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,治安支队,外事科','这是最先的25万条记录')

    set @i=@i+1

    end

    GO

    declare @i int

    set @i=1

    while @i<=250000

    begin

    insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-9-16','办公室','办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,外事科','这是中间的25万条记录')

    set @i=@i+1

    end

    GO

    declare @h int

    set @h=1

    while @h<=100

    begin

    declare @i int

    set @i=2002

    while @i<=2003

    begin

    declare @j int

    set @j=0

    while @j<50

    begin

    declare @k int

    set @k=0

    while @k<50

    begin

    insert into Tgongwen(fariqi,neibuyonghu,reader,title) values(cast(@i as varchar(4))+'-8-15 3:'+cast(@j as varchar(2))+':'+cast(@j as varchar(2)),'通信科','办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,外事科','这是最后的50万条记录')

    set @k=@k+1

    end

    set @j=@j+1

    end

    set @i=@i+1

    end

    set @h=@h+1

    end

    GO

    declare @i int

    set @i=1

    while @i<=9000000

    begin

    insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-5-5','通信科','通信科,办公室,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,治安支队,外事科','这是最后添加的900万条记录')

    set @i=@i+1000000

    end

    GO

    通过以上语句,我们创建了25万条由通信科于2004年2月5日发布的记录,25万条由办公室于2004年9月6日发布的记录,2002年和2003年各100个2500条相同日期、不同分秒的由通信科发布的记录(共50万条),还有由通信科于2004年5月5日发布的900万条记录,合计1000万条。

    一、因情制宜,建立“适当”的索引

    建立“适当”的索引是实现查询优化的首要前提。

    索引(index)是除表之外另一重要的、用户定义的存储在物理介质上的数据结构。当根据索引码的值搜索数据时,索引提供了对数据的快速访问。事实上,没有索引,数据库也能根据SELECT语句成功地检索到结果,但随着表变得越来越大,使用“适当”的索引的效果就越来越明显。注意,在这句话中,我们用了“适当”这个词,这是因为,如果使用索引时不认真考虑其实现过程,索引既可以提高也会破坏数据库的工作性能。

    (一)深入浅出理解索引结构

    实际上,您可以把索引理解为一种特殊的目录。微软的SQL SERVER提供了两种索引:聚集索引(clustered index,也称聚类索引、簇集索引)和非聚集索引(nonclustered index,也称非聚类索引、非簇集索引)。下面,我们举例来说明一下聚集索引和非聚集索引的区别:

    其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查“张”字,那您也会将您的字典翻到最后部分,因为“张”的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。

    我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。

    如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。

    我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。

    通过以上例子,我们可以理解到什么是“聚集索引”和“非聚集索引”。

    进一步引申一下,我们可以很容易的理解:每个表只能有一个聚集索引,因为目录只能按照一种方法进行排序。

    (二)何时使用聚集索引或非聚集索引

    下面的表总结了何时使用聚集索引或非聚集索引(很重要)。

    动作描述
    使用聚集索引
    使用非聚集索引

    列经常被分组排序



    返回某范围内的数据

    不应

    一个或极少不同值
    不应
    不应

    小数目的不同值

    不应

    大数目的不同值
    不应


    频繁更新的列
    不应


    外键列



    主键列



    频繁修改索引列
    不应


    事实上,我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。如:返回某范围内的数据一项。比如您的某个表有一个时间列,恰好您把聚合索引建立在了该列,这时您查询2004年1月1日至2004年10月1日之间的全部数据时,这个速度就将是很快的,因为您的这本字典正文是按日期进行排序的,聚类索引只需要找到要检索的所有数据中的开头和结尾数据即可;而不像非聚集索引,必须先查到目录中查到每一项数据对应的页码,然后再根据页码查到具体内容。

    (三)结合实际,谈索引使用的误区

    理论的目的是应用。虽然我们刚才列出了何时应使用聚集索引或非聚集索引,但在实践中以上规则却很容易被忽视或不能根据实际情况进行综合分析。下面我们将根据在实践中遇到的实际问题来谈一下索引使用的误区,以便于大家掌握索引建立的方法。

    1、主键就是聚集索引

    这种想法笔者认为是极端错误的,是对聚集索引的一种浪费。虽然SQL SERVER默认是在主键上建立聚集索引的。

    通常,我们会在每个表中都建立一个ID列,以区分每条数据,并且这个ID列是自动增大的,步长一般为1。我们的这个办公自动化的实例中的列Gid就是如此。此时,如果我们将这个列设为主键,SQL SERVER会将此列默认为聚集索引。这样做有好处,就是可以让您的数据在数据库中按照ID进行物理排序,但笔者认为这样做意义不大。

    显而易见,聚集索引的优势是很明显的,而每个表中只能有一个聚集索引的规则,这使得聚集索引变得更加珍贵。

    从我们前面谈到的聚集索引的定义我们可以看出,使用聚集索引的最大好处就是能够根据查询要求,迅速缩小查询范围,避免全表扫描。在实际应用中,因为ID号是自动生成的,我们并不知道每条记录的ID号,所以我们很难在实践中用ID号来进行查询。这就使让ID号这个主键作为聚集索引成为一种资源浪费。其次,让每个ID号都不同的字段作为聚集索引也不符合“大数目的不同值情况下不应建立聚合索引”规则;当然,这种情况只是针对用户经常修改记录内容,特别是索引项的时候会负作用,但对于查询速度并没有影响。

    在办公自动化系统中,无论是系统首页显示的需要用户签收的文件、会议还是用户进行文件查询等任何情况下进行数据查询都离不开字段的是“日期”还有用户本身的“用户名”。

    通常,办公自动化的首页会显示每个用户尚未签收的文件或会议。虽然我们的where语句可以仅仅限制当前用户尚未签收的情况,但如果您的系统已建立了很长时间,并且数据量很大,那么,每次每个用户打开首页的时候都进行一次全表扫描,这样做意义是不大的,绝大多数的用户1个月前的文件都已经浏览过了,这样做只能徒增数据库的开销而已。事实上,我们完全可以让用户打开系统首页时,数据库仅仅查询这个用户近3个月来未阅览的文件,通过“日期”这个字段来限制表扫描,提高查询速度。如果您的办公自动化系统已经建立的2年,那么您的首页显示速度理论上将是原来速度8倍,甚至更快。

    在这里之所以提到“理论上”三字,是因为如果您的聚集索引还是盲目地建在ID这个主键上时,您的查询速度是没有这么高的,即使您在“日期”这个字段上建立的索引(非聚合索引)。下面我们就来看一下在1000万条数据量的情况下各种查询的速度表现(3个月内的数据为25万条):

    (1)仅在主键上建立聚集索引,并且不划分时间段:

    Select gid,fariqi,neibuyonghu,title from tgongwen

    用时:128470毫秒(即:128秒)

    (2)在主键上建立聚集索引,在fariq上建立非聚集索引:

    select gid,fariqi,neibuyonghu,title from Tgongwen

    where fariqi> dateadd(day,-90,getdate())

    用时:53763毫秒(54秒)

    (3)将聚合索引建立在日期列(fariqi)上:

    select gid,fariqi,neibuyonghu,title from Tgongwen

    where fariqi> dateadd(day,-90,getdate())

    用时:2423毫秒(2秒)

    虽然每条语句提取出来的都是25万条数据,各种情况的差异却是巨大的,特别是将聚集索引建立在日期列时的差异。事实上,如果您的数据库真的有1000万容量的话,把主键建立在ID列上,就像以上的第1、2种情况,在网页上的表现就是超时,根本就无法显示。这也是我摒弃ID列作为聚集索引的一个最重要的因素。

    得出以上速度的方法是:在各个select语句前加:declare @d datetime

    set @d=getdate()

    并在select语句后加:

    select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate())

    2、只要建立索引就能显著提高查询速度

    事实上,我们可以发现上面的例子中,第2、3条语句完全相同,且建立索引的字段也相同;不同的仅是前者在fariqi字段上建立的是非聚合索引,后者在此字段上建立的是聚合索引,但查询速度却有着天壤之别。所以,并非是在任何字段上简单地建立索引就能提高查询速度。

    从建表的语句中,我们可以看到这个有着1000万数据的表中fariqi字段有5003个不同记录。在此字段上建立聚合索引是再合适不过了。在现实中,我们每天都会发几个文件,这几个文件的发文日期就相同,这完全符合建立聚集索引要求的:“既不能绝大多数都相同,又不能只有极少数相同”的规则。由此看来,我们建立“适当”的聚合索引对于我们提高查询速度是非常重要的。

    3、把所有需要提高查询速度的字段都加进聚集索引,以提高查询速度

    上面已经谈到:在进行数据查询时都离不开字段的是“日期”还有用户本身的“用户名”。既然这两个字段都是如此的重要,我们可以把他们合并起来,建立一个复合索引(compound index)。

    很多人认为只要把任何字段加进聚集索引,就能提高查询速度,也有人感到迷惑:如果把复合的聚集索引字段分开查询,那么查询速度会减慢吗?带着这个问题,我们来看一下以下的查询速度(结果集都是25万条数据):(日期列fariqi首先排在复合聚集索引的起始列,用户名neibuyonghu排在后列)

    (1)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5'

    查询速度:2513毫秒

    (2)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5' and neibuyonghu='办公室'

    查询速度:2516毫秒

    (3)select gid,fariqi,neibuyonghu,title from Tgongwen where neibuyonghu='办公室'

    查询速度:60280毫秒

    从以上试验中,我们可以看到如果仅用聚集索引的起始列作为查询条件和同时用到复合聚集索引的全部列的查询速度是几乎一样的,甚至比用上全部的复合索引列还要略快(在查询结果集数目一样的情况下);而如果仅用复合聚集索引的非起始列作为查询条件的话,这个索引是不起任何作用的。当然,语句1、2的查询速度一样是因为查询的条目数一样,如果复合索引的所有列都用上,而且查询结果少的话,这样就会形成“索引覆盖”,因而性能可以达到最优。同时,请记住:无论您是否经常使用聚合索引的其他列,但其前导列一定要是使用最频繁的列。

    (四)其他书上没有的索引使用经验总结

    1、用聚合索引比用不是聚合索引的主键速度快

    下面是实例语句:(都是提取25万条数据)

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'

    使用时间:3326毫秒

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid<=250000

    使用时间:4470毫秒

    这里,用聚合索引比用不是聚合索引的主键速度快了近1/4。

    2、用聚合索引比用一般的主键作order by时速度快,特别是在小数据量情况下

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by fariqi

    用时:12936

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by gid

    用时:18843

    这里,用聚合索引比用一般的主键作order by时,速度快了3/10。事实上,如果数据量很小的话,用聚集索引作为排序列要比使用非聚集索引速度快得明显的多;而数据量如果很大的话,如10万以上,则二者的速度差别不明显。

    3、使用聚合索引内的时间段,搜索时间会按数据占整个数据表的百分比成比例减少,而无论聚合索引使用了多少个

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1'

    用时:6343毫秒(提取100万条)

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-6-6'

    用时:3170毫秒(提取50万条)

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'

    用时:3326毫秒(和上句的结果一模一样。如果采集的数量一样,那么用大于号和等于号是一样的)

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' and fariqi<'2004-6-6'

    用时:3280毫秒

    4 、日期列不会因为有分秒的输入而减慢查询速度

    下面的例子中,共有100万条数据,2004年1月1日以后的数据有50万条,但只有两个不同的日期,日期精确到日;之前有数据50万条,有5000个不同的日期,日期精确到秒。

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' order by fariqi

    用时:6390毫秒

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi<'2004-1-1' order by fariqi

    用时:6453毫秒

    (五)其他注意事项

    “水可载舟,亦可覆舟”,索引也一样。索引有助于提高检索性能,但过多或不当的索引也会导致系统低效。因为用户在表中每加进一个索引,数据库就要做更多的工作。过多的索引甚至会导致索引碎片。

    所以说,我们要建立一个“适当”的索引体系,特别是对聚合索引的创建,更应精益求精,以使您的数据库能得到高性能的发挥。

    当然,在实践中,作为一个尽职的数据库管理员,您还要多测试一些方案,找出哪种方案效率最高、最为有效。

    二、改善SQL语句

    很多人不知道SQL语句在SQL SERVER中是如何执行的,他们担心自己所写的SQL语句会被SQL SERVER误解。比如:

    select * from table1 where name='zhangsan' and tID > 10000

    和执行:

    select * from table1 where tID > 10000 and name='zhangsan'

    一些人不知道以上两条语句的执行效率是否一样,因为如果简单的从语句先后上看,这两个语句的确是不一样,如果tID是一个聚合索引,那么后一句仅仅从表的10000条以后的记录中查找就行了;而前一句则要先从全表中查找看有几个name='zhangsan'的,而后再根据限制条件条件tID>10000来提出查询结果。

    事实上,这样的担心是不必要的。SQL SERVER中有一个“查询分析优化器”,它可以计算出where子句中的搜索条件并确定哪个索引能缩小表扫描的搜索空间,也就是说,它能实现自动优化。

    虽然查询优化器可以根据where子句自动的进行查询优化,但大家仍然有必要了解一下“查询优化器”的工作原理,如非这样,有时查询优化器就会不按照您的本意进行快速查询。

    在查询分析阶段,查询优化器查看查询的每个阶段并决定限制需要扫描的数据量是否有用。如果一个阶段可以被用作一个扫描参数(SARG),那么就称之为可优化的,并且可以利用索引快速获得所需数据。

    SARG的定义:用于限制搜索的一个操作,因为它通常是指一个特定的匹配,一个值得范围内的匹配或者两个以上条件的AND连接。形式如下:

    列名 操作符 <常数 或 变量>



    <常数 或 变量> 操作符列名

    列名可以出现在操作符的一边,而常数或变量出现在操作符的另一边。如:

    Name=’张三’

    价格>5000

    5000<价格

    Name=’张三’ and 价格>5000

    如果一个表达式不能满足SARG的形式,那它就无法限制搜索的范围了,也就是SQL SERVER必须对每一行都判断它是否满足WHERE子句中的所有条件。所以一个索引对于不满足SARG形式的表达式来说是无用的。

    介绍完SARG后,我们来总结一下使用SARG以及在实践中遇到的和某些资料上结论不同的经验:

    1、Like语句是否属于SARG取决于所使用的通配符的类型

    如:name like ‘张%’ ,这就属于SARG

    而:name like ‘%张’ ,就不属于SARG。

    原因是通配符%在字符串的开通使得索引无法使用。

    2、or 会引起全表扫描

    Name=’张三’ and 价格>5000 符号SARG,而:Name=’张三’ or 价格>5000 则不符合SARG。使用or会引起全表扫描。

    3、非操作符、函数引起的不满足SARG形式的语句

    不满足SARG形式的语句最典型的情况就是包括非操作符的语句,如:NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE等,另外还有函数。下面就是几个不满足SARG形式的例子:

    ABS(价格)<5000

    Name like ‘%三’

    有些表达式,如:

    WHERE 价格*2>5000

    SQL SERVER也会认为是SARG,SQL SERVER会将此式转化为:

    WHERE 价格>2500/2

    但我们不推荐这样使用,因为有时SQL SERVER不能保证这种转化与原始表达式是完全等价的。

    4、IN 的作用相当与OR

    语句:

    Select * from table1 where tid in (2,3)



    Select * from table1 where tid=2 or tid=3

    是一样的,都会引起全表扫描,如果tid上有索引,其索引也会失效。

    5、尽量少用NOT

    6、exists 和 in 的执行效率是一样的

    很多资料上都显示说,exists要比in的执行效率要高,同时应尽可能的用not exists来代替not in。但事实上,我试验了一下,发现二者无论是前面带不带not,二者之间的执行效率都是一样的。因为涉及子查询,我们试验这次用SQL SERVER自带的pubs数据库。运行前我们可以把SQL SERVER的statistics I/O状态打开。

    (1)select title,price from titles where title_id in (select title_id from sales where qty>30)

    该句的执行结果为:

    表 'sales'。扫描计数 18,逻辑读 56 次,物理读 0 次,预读 0 次。

    表 'titles'。扫描计数 1,逻辑读 2 次,物理读 0 次,预读 0 次。

    (2)select title,price from titles where exists (select * from sales where sales.title_id=titles.title_id and qty>30)

    第二句的执行结果为:

    表 'sales'。扫描计数 18,逻辑读 56 次,物理读 0 次,预读 0 次。

    表 'titles'。扫描计数 1,逻辑读 2 次,物理读 0 次,预读 0 次。

    我们从此可以看到用exists和用in的执行效率是一样的。

    7、用函数charindex()和前面加通配符%的LIKE执行效率一样

    前面,我们谈到,如果在LIKE前面加上通配符%,那么将会引起全表扫描,所以其执行效率是低下的。但有的资料介绍说,用函数charindex()来代替LIKE速度会有大的提升,经我试验,发现这种说明也是错误的:

    select gid,title,fariqi,reader from tgongwen where charindex('刑侦支队',reader)>0 and fariqi>'2004-5-5'

    用时:7秒,另外:扫描计数 4,逻辑读 7155 次,物理读 0 次,预读 0 次。

    select gid,title,fariqi,reader from tgongwen where reader like '%' + '刑侦支队' + '%' and fariqi>'2004-5-5'

    用时:7秒,另外:扫描计数 4,逻辑读 7155 次,物理读 0 次,预读 0 次。

    8、union并不绝对比or的执行效率高

    我们前面已经谈到了在where子句中使用or会引起全表扫描,一般的,我所见过的资料都是推荐这里用union来代替or。事实证明,这种说法对于大部分都是适用的。

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or gid>9990000

    用时:68秒。扫描计数 1,逻辑读 404008 次,物理读 283 次,预读 392163 次。

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'

    union

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid>9990000

    用时:9秒。扫描计数 8,逻辑读 67489 次,物理读 216 次,预读 7499 次。

    看来,用union在通常情况下比用or的效率要高的多。

    但经过试验,笔者发现如果or两边的查询列是一样的话,那么用union则反倒和用or的执行速度差很多,虽然这里union扫描的是索引,而or扫描的是全表。

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or fariqi='2004-2-5'

    用时:6423毫秒。扫描计数 2,逻辑读 14726 次,物理读 1 次,预读 7176 次。

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'

    union

    select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-2-5'

    用时:11640毫秒。扫描计数 8,逻辑读 14806 次,物理读 108 次,预读 1144 次。

    9、字段提取要按照“需多少、提多少”的原则,避免“select *”

    我们来做一个试验:

    select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc

    用时:4673毫秒

    select top 10000 gid,fariqi,title from tgongwen order by gid desc

    用时:1376毫秒

    select top 10000 gid,fariqi from tgongwen order by gid desc

    用时:80毫秒

    由此看来,我们每少提取一个字段,数据的提取速度就会有相应的提升。提升的速度还要看您舍弃的字段的大小来判断。

    10、count(*)不比count(字段)慢

    某些资料上说:用*会统计所有列,显然要比一个世界的列名效率低。这种说法其实是没有根据的。我们来看:

    select count(*) from Tgongwen

    用时:1500毫秒

    select count(gid) from Tgongwen

    用时:1483毫秒

    select count(fariqi) from Tgongwen

    用时:3140毫秒

    select count(title) from Tgongwen

    用时:52050毫秒

    从以上可以看出,如果用count(*)和用count(主键)的速度是相当的,而count(*)却比其他任何除主键以外的字段汇总速度要快,而且字段越长,汇总的速度就越慢。我想,如果用count(*), SQL SERVER可能会自动查找最小字段来汇总的。当然,如果您直接写count(主键)将会来的更直接些。

    11、order by按聚集索引列排序效率最高

    我们来看:(gid是主键,fariqi是聚合索引列)

    select top 10000 gid,fariqi,reader,title from tgongwen

    用时:196 毫秒。 扫描计数 1,逻辑读 289 次,物理读 1 次,预读 1527 次。

    select top 10000 gid,fariqi,reader,title from tgongwen order by gid asc

    用时:4720毫秒。 扫描计数 1,逻辑读 41956 次,物理读 0 次,预读 1287 次。

    select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc

    用时:4736毫秒。 扫描计数 1,逻辑读 55350 次,物理读 10 次,预读 775 次。

    select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi asc

    用时:173毫秒。 扫描计数 1,逻辑读 290 次,物理读 0 次,预读 0 次。

    select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi desc

    用时:156毫秒。 扫描计数 1,逻辑读 289 次,物理读 0 次,预读 0 次。

    从以上我们可以看出,不排序的速度以及逻辑读次数都是和“order by 聚集索引列” 的速度是相当的,但这些都比“order by 非聚集索引列”的查询速度是快得多的。

    同时,按照某个字段进行排序的时候,无论是正序还是倒序,速度是基本相当的。

    12、高效的TOP

    事实上,在查询和提取超大容量的数据集时,影响数据库响应时间的最大因素不是数据查找,而是物理的I/0操作。如:

    select top 10 * from (

    select top 10000 gid,fariqi,title from tgongwen

    where neibuyonghu='办公室'

    order by gid desc) as a

    order by gid asc

    这条语句,从理论上讲,整条语句的执行时间应该比子句的执行时间长,但事实相反。因为,子句执行后返回的是10000条记录,而整条语句仅返回10条语句,所以影响数据库响应时间最大的因素是物理I/O操作。而限制物理I/O操作此处的最有效方法之一就是使用TOP关键词了。TOP关键词是SQL SERVER中经过系统优化过的一个用来提取前几条或前几个百分比数据的词。经笔者在实践中的应用,发现TOP确实很好用,效率也很高。但这个词在另外一个大型数据库ORACLE中却没有,这不能说不是一个遗憾,虽然在ORACLE中可以用其他方法(如:rownumber)来解决。在以后的关于“实现千万级数据的分页显示存储过程”的讨论中,我们就将用到TOP这个关键词。



    到此为止,我们上面讨论了如何实现从大容量的数据库中快速地查询出您所需要的数据方法。当然,我们介绍的这些方法都是“软”方法,在实践中,我们还要考虑各种“硬”因素,如:网络性能、服务器的性能、操作系统的性能,甚至网卡、交换机等。

    三、实现小数据量和海量数据的通用分页显示存储过程

    建立一个web 应用,分页浏览功能必不可少。这个问题是数据库处理中十分常见的问题。经典的数据分页方法是:ADO 纪录集分页法,也就是利用ADO自带的分页功能(利用游标)来实现分页。但这种分页方法仅适用于较小数据量的情形,因为游标本身有缺点:游标是存放在内存中,很费内存。游标一建立,就将相关的记录锁住,直到取消游标。游标提供了对特定集合中逐行扫描的手段,一般使用游标来逐行遍历数据,根据取出数据条件的不同进行不同的操作。而对于多表和大表中定义的游标(大的数据集合)循环很容易使程序进入一个漫长的等待甚至死机。

    更重要的是,对于非常大的数据模型而言,分页检索时,如果按照传统的每次都加载整个数据源的方法是非常浪费资源的。现在流行的分页方法一般是检索页面大小的块区的数据,而非检索所有的数据,然后单步执行当前行。

    最早较好地实现这种根据页面大小和页码来提取数据的方法大概就是“俄罗斯存储过程”。这个存储过程用了游标,由于游标的局限性,所以这个方法并没有得到大家的普遍认可。

    后来,网上有人改造了此存储过程,下面的存储过程就是结合我们的办公自动化实例写的分页存储过程:

    CREATE procedure pagination1

    (@pagesize int, --页面大小,如每页存储20条记录

    @pageindex int --当前页码

    )

    as

    set nocount on

    begin

    declare @indextable table(id int identity(1,1),nid int) --定义表变量

    declare @PageLowerBound int --定义此页的底码

    declare @PageUpperBound int --定义此页的顶码

    set @PageLowerBound=(@pageindex-1)*@pagesize

    set @PageUpperBound=@PageLowerBound+@pagesize

    set rowcount @PageUpperBound

    insert into @indextable(nid) select gid from TGongwen where fariqi >dateadd(day,-365,getdate()) order by fariqi desc

    select O.gid,O.mid,O.title,O.fadanwei,O.fariqi from TGongwen O,@indextable t where O.gid=t.nid

    and t.id>@PageLowerBound and t.id<=@PageUpperBound order by t.id

    end

    set nocount off

    以上存储过程运用了SQL SERVER的最新技术――表变量。应该说这个存储过程也是一个非常优秀的分页存储过程。当然,在这个过程中,您也可以把其中的表变量写成临时表:CREATE TABLE #Temp。但很明显,在SQL SERVER中,用临时表是没有用表变量快的。所以笔者刚开始使用这个存储过程时,感觉非常的不错,速度也比原来的ADO的好。但后来,我又发现了比此方法更好的方法。

    笔者曾在网上看到了一篇小短文《从数据表中取出第n条到第m条的记录的方法》,全文如下:

    从publish 表中取出第 n 条到第 m 条的记录:
    SELECT TOP m-n+1 *
    FROM publish
    WHERE (id NOT IN
        (SELECT TOP n-1 id
         FROM publish))

    id 为publish 表的关键字

    我当时看到这篇文章的时候,真的是精神为之一振,觉得思路非常得好。等到后来,我在作办公自动化系统(ASP.NET+ C#+SQL SERVER)的时候,忽然想起了这篇文章,我想如果把这个语句改造一下,这就可能是一个非常好的分页存储过程。于是我就满网上找这篇文章,没想到,文章还没找到,却找到了一篇根据此语句写的一个分页存储过程,这个存储过程也是目前较为流行的一种分页存储过程,我很后悔没有争先把这段文字改造成存储过程:

    CREATE PROCEDURE pagination2
    (
    @SQL nVARCHAR(4000), --不带排序语句的SQL语句
    @Page int, --页码
    @RecsPerPage int, --每页容纳的记录数
    @ID VARCHAR(255), --需要排序的不重复的ID号
    @Sort VARCHAR(255) --排序字段及规则
    )
    AS

    DECLARE @Str nVARCHAR(4000)

    SET @Str='SELECT TOP '+CAST(@RecsPerPage AS VARCHAR(20))+' * FROM ('+@SQL+') T WHERE T.'+@ID+'NOT IN
    (SELECT TOP '+CAST((@RecsPerPage*(@Page-1)) AS VARCHAR(20))+' '+@ID+' FROM ('+@SQL+') T9 ORDER BY '+@Sort+') ORDER BY '+@Sort

    PRINT @Str

    EXEC sp_ExecuteSql @Str
    GO

    其实,以上语句可以简化为:

    SELECT TOP 页大小 *

    FROM Table1

    WHERE (ID NOT IN

    (SELECT TOP 页大小*页数 id

    FROM 表

    ORDER BY id))

    ORDER BY ID

    但这个存储过程有一个致命的缺点,就是它含有NOT IN字样。虽然我可以把它改造为:

    SELECT TOP 页大小 *

    FROM Table1

    WHERE not exists

    (select * from (select top (页大小*页数) * from table1 order by id) b where b.id=a.id )

    order by id

    即,用not exists来代替not in,但我们前面已经谈过了,二者的执行效率实际上是没有区别的。

    既便如此,用TOP 结合NOT IN的这个方法还是比用游标要来得快一些。

    虽然用not exists并不能挽救上个存储过程的效率,但使用SQL SERVER中的TOP关键字却是一个非常明智的选择。因为分页优化的最终目的就是避免产生过大的记录集,而我们在前面也已经提到了TOP的优势,通过TOP 即可实现对数据量的控制。

    在分页算法中,影响我们查询速度的关键因素有两点:TOP和NOT IN。TOP可以提高我们的查询速度,而NOT IN会减慢我们的查询速度,所以要提高我们整个分页算法的速度,就要彻底改造NOT IN,同其他方法来替代它。

    我们知道,几乎任何字段,我们都可以通过max(字段)或min(字段)来提取某个字段中的最大或最小值,所以如果这个字段不重复,那么就可以利用这些不重复的字段的max或min作为分水岭,使其成为分页算法中分开每页的参照物。在这里,我们可以用操作符“>”或“<”号来完成这个使命,使查询语句符合SARG形式。如:

    Select top 10 * from table1 where id>200

    于是就有了如下分页方案:

    select top 页大小 *

    from table1

    where id>

    (select max (id) from

    (select top ((页码-1)*页大小) id from table1 order by id) as T

    )

    order by id

    在选择即不重复值,又容易分辨大小的列时,我们通常会选择主键。下表列出了笔者用有着1000万数据的办公自动化系统中的表,在以GID(GID是主键,但并不是聚集索引。)为排序列、提取gid,fariqi,title字段,分别以第1、10、100、500、1000、1万、10万、25万、50万页为例,测试以上三种分页方案的执行速度:(单位:毫秒)

    页 码
    方案1
    方案2
    方案3

    1
    60
    30
    76

    10
    46
    16
    63

    100
    1076
    720
    130

    500
    540
    12943
    83

    1000
    17110
    470
    250

    1万
    24796
    4500
    140

    10万
    38326
    42283
    1553

    25万
    28140
    128720
    2330

    50万
    121686
    127846
    7168


    从上表中,我们可以看出,三种存储过程在执行100页以下的分页命令时,都是可以信任的,速度都很好。但第一种方案在执行分页1000页以上后,速度就降了下来。第二种方案大约是在执行分页1万页以上后速度开始降了下来。而第三种方案却始终没有大的降势,后劲仍然很足。

    在确定了第三种分页方案后,我们可以据此写一个存储过程。大家知道SQL SERVER的存储过程是事先编译好的SQL语句,它的执行效率要比通过WEB页面传来的SQL语句的执行效率要高。下面的存储过程不仅含有分页方案,还会根据页面传来的参数来确定是否进行数据总数统计。

    -- 获取指定页的数据

    CREATE PROCEDURE pagination3

    @tblName varchar(255), -- 表名

    @strGetFields varchar(1000) = '*', -- 需要返回的列

    @fldName varchar(255)='', -- 排序的字段名

    @PageSize int = 10, -- 页尺寸

    @PageIndex int = 1, -- 页码

    @doCount bit = 0, -- 返回记录总数, 非 0 值则返回

    @OrderType bit = 0, -- 设置排序类型, 非 0 值则降序

    @strWhere varchar(1500) = '' -- 查询条件 (注意: 不要加 where)

    AS

    declare @strSQL varchar(5000) -- 主语句

    declare @strTmp varchar(110) -- 临时变量

    declare @strOrder varchar(400) -- 排序类型

    if @doCount != 0

    begin

    if @strWhere !=''

    set @strSQL = "select count(*) as Total from [" + @tblName + "] where "+@strWhere

    else

    set @strSQL = "select count(*) as Total from [" + @tblName + "]"

    end

    --以上代码的意思是如果@doCount传递过来的不是0,就执行总数统计。以下的所有代码都是@doCount为0的情况

    else

    begin

    if @OrderType != 0

    begin

    set @strTmp = "<(select min"

    set @strOrder = " order by [" + @fldName +"] desc"

    --如果@OrderType不是0,就执行降序,这句很重要!

    end

    else

    begin

    set @strTmp = ">(select max"

    set @strOrder = " order by [" + @fldName +"] asc"

    end

    if @PageIndex = 1

    begin

    if @strWhere != ''

    set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from [" + @tblName + "] where " + @strWhere + " " + @strOrder

    else

    set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["+ @tblName + "] "+ @strOrder

    --如果是第一页就执行以上代码,这样会加快执行速度

    end

    else

    begin

    --以下代码赋予了@strSQL以真正执行的SQL代码

    set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["

    + @tblName + "] where [" + @fldName + "]" + @strTmp + "(["+ @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["+ @fldName + "] from [" + @tblName + "]" + @strOrder + ") as tblTmp)"+ @strOrder

    if @strWhere != ''

    set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["

    + @tblName + "] where [" + @fldName + "]" + @strTmp + "(["

    + @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["

    + @fldName + "] from [" + @tblName + "] where " + @strWhere + " "

    + @strOrder + ") as tblTmp) and " + @strWhere + " " + @strOrder

    end

    end

    exec (@strSQL)

    GO

    上面的这个存储过程是一个通用的存储过程,其注释已写在其中了。

    在大数据量的情况下,特别是在查询最后几页的时候,查询时间一般不会超过9秒;而用其他存储过程,在实践中就会导致超时,所以这个存储过程非常适用于大容量数据库的查询。

    笔者希望能够通过对以上存储过程的解析,能给大家带来一定的启示,并给工作带来一定的效率提升,同时希望同行提出更优秀的实时数据分页算法。



    四、聚集索引的重要性和如何选择聚集索引

    在上一节的标题中,笔者写的是:实现小数据量和海量数据的通用分页显示存储过程。这是因为在将本存储过程应用于“办公自动化”系统的实践中时,笔者发现这第三种存储过程在小数据量的情况下,有如下现象:

    1、分页速度一般维持在1秒和3秒之间。

    2、在查询最后一页时,速度一般为5秒至8秒,哪怕分页总数只有3页或30万页。

    虽然在超大容量情况下,这个分页的实现过程是很快的,但在分前几页时,这个1-3秒的速度比起第一种甚至没有经过优化的分页方法速度还要慢,借用户的话说就是“还没有ACCESS数据库速度快”,这个认识足以导致用户放弃使用您开发的系统。

    笔者就此分析了一下,原来产生这种现象的症结是如此的简单,但又如此的重要:排序的字段不是聚集索引!

    本篇文章的题目是:“查询优化及分页算法方案”。笔者只所以把“查询优化”和“分页算法”这两个联系不是很大的论题放在一起,就是因为二者都需要一个非常重要的东西――聚集索引。

    在前面的讨论中我们已经提到了,聚集索引有两个最大的优势:

    1、以最快的速度缩小查询范围。

    2、以最快的速度进行字段排序。

    第1条多用在查询优化时,而第2条多用在进行分页时的数据排序。

    而聚集索引在每个表内又只能建立一个,这使得聚集索引显得更加的重要。聚集索引的挑选可以说是实现“查询优化”和“高效分页”的最关键因素。

    但要既使聚集索引列既符合查询列的需要,又符合排序列的需要,这通常是一个矛盾。

    笔者前面“索引”的讨论中,将fariqi,即用户发文日期作为了聚集索引的起始列,日期的精确度为“日”。这种作法的优点,前面已经提到了,在进行划时间段的快速查询中,比用ID主键列有很大的优势。

    但在分页时,由于这个聚集索引列存在着重复记录,所以无法使用max或min来最为分页的参照物,进而无法实现更为高效的排序。而如果将ID主键列作为聚集索引,那么聚集索引除了用以排序之外,没有任何用处,实际上是浪费了聚集索引这个宝贵的资源。

    为解决这个矛盾,笔者后来又添加了一个日期列,其默认值为getdate()。用户在写入记录时,这个列自动写入当时的时间,时间精确到毫秒。即使这样,为了避免可能性很小的重合,还要在此列上创建UNIQUE约束。将此日期列作为聚集索引列。

    有了这个时间型聚集索引列之后,用户就既可以用这个列查找用户在插入数据时的某个时间段的查询,又可以作为唯一列来实现max或min,成为分页算法的参照物。

    经过这样的优化,笔者发现,无论是大数据量的情况下还是小数据量的情况下,分页速度一般都是几十毫秒,甚至0毫秒。而用日期段缩小范围的查询速度比原来也没有任何迟钝。

    聚集索引是如此的重要和珍贵,所以笔者总结了一下,一定要将聚集索引建立在:

    1、您最频繁使用的、用以缩小查询范围的字段上;

    2、您最频繁使用的、需要排序的字段上。


    结束语:

    本篇文章汇集了笔者近段在使用数据库方面的心得,是在做“办公自动化”系统时实践经验的积累。希望这篇文章不仅能够给大家的工作带来一定的帮助,也希望能让大家能够体会到分析问题的方法;最重要的是,希望这篇文章能够抛砖引玉,掀起大家的学习和讨论的兴趣,以共同促进,共同为公安科技强警事业和金盾工程做出自己最大的努力。

    最后需要说明的是,在试验中,我发现用户在进行大数据量查询的时候,对数据库速度影响最大的不是内存大小,而是CPU。在我的P4 2.4机器上试验的时候,查看“资源管理器”,CPU经常出现持续到100%的现象,而内存用量却并没有改变或者说没有大的改变。即使在我们的HP ML 350 G3服务器上试验时,CPU峰值也能达到90%,一般持续在70%左右。

    posted @ 2007-05-23 23:28 鸿雁| 编辑 收藏

    Web Service学习笔记之----JAX-RPC (转载CN-JAVA)

    导读
    本文是我对学习jwsdp-1.2时所做笔记的整理,其中主要是一些指导性的内容,并没有多少概念以及原理的介绍,读者可能觉得略显简单,如果想要学习基本概念可以参考网上有关Web Service的资料。本文例子所使用的开发环境是WindowXP+JWSDP-1.2。

    一.Web Service简介
    1.定义
    由两部分组成
    ·SOAP--Web Service之间的基本通信协议。
    ·WSDL--Web Service描述语言,它定义了Web Service做什么,怎么做和查询的信息。
    2.简单的Web Service实现
    包含四个基本步骤
    ·创建Web Service的商业逻辑(通常是一些Java类)
    ·将这些Java类部署到一个SOAP服务器上
    ·生成客户访问代码
    ·部署客户应用
    注意:WSDL等文件的生成通常是利用厂商提供的工具来完成
    3.WSDL解析
    WSDL描述语言一般包含三部分
    ·What部分--包括了type、message和portType元素
    Type:定义了Web Service使用的数据结构(使用XML Schema定义)
    Message:一个Message是SOAP的基本通信元素。每个Message可以有一个或多个Part,每个Part代表一个参数。
    PortType:消息汇总为不同的操作并归入到一个被称为portType的实体中。一个portType代表一个接口(Web Service支 持的操作集合),每个Web Service可以有多个接口,它们都使用portType表示。每个操作又包含了input和 output部分。
    ·How部分--包含binding元素
    binding元素将portType绑定到特定的通信协议上(如HTTP上的SOAP协议)
    ·Where部分--由service元素组成
    它将portType,binding以及Web Service实际的位置(URI)放在一起描述
    4.客户端
    通常Web Service可以有三种类型的客户
    ·商业伙伴(Business Partner)--包括分发商,零售商以及大型消费者)
    此类客户通过SOAP、WSDL、ebXML、UDDI等XML技术与Web Service连接
    ·瘦客户--包括Web浏览器、PDA以及无线设备
    该类客户通常经由轻量协议(如HTTP)与Web Service连接
    ·肥客户--包括Applet、各类应用以及现存系统
    通常使用重量级协议(如IIOP)连接Web Service

    二.使用JAX-RPC开发Web Service
    1.JAX-RPC支持的数据类型
    JAX-RPC除了支持Java的基本数据类型外还支持一些自定义对象,但这些对象有一些条件限制
    ·有缺省构造函数的对象
    ·没有实现java.rmi.Remote接口
    ·字段必须是JAX-RPC支持的类型
    ·公有字段不能声明为final或transient
    ·非公有字段必须有对应的setter和getter方法
    2.使用JAX-RPC创建Web Service
    ·基本步骤
    A. 编写服务端接口并实现
    一个服务的end-point有一些规定:必须实现java.rmi.Remot接口而且每个方法需要抛出RemoteException异常。
    B. 编译、生成并且将所有服务需要的类和文件打包成WAR文件
    C. 部署包含服务的WAR文件
    ·如何创建服务
    A. 编译服务所需的类文件
    B. 生成服务所需文件
    可以使用wscompile工具生成model.gz文件,它包含了描述服务的内部数据结构命令如下
    wscompile -define -d build -nd build -classpath build config.xml
    -model build/model.gz
    define标志告诉工具读取服务的 endpoint接口并且创建WSDL文件。-d和-nd标志告诉工具将输出文件写入指定的目录build。工具需要读以下的config.xml文件
    <?xml version=”1.0” encoding=”UTF-8”?>
    <configuration xmlns=”http://java.sun.com/xml/ns/jax-rpc/ri/config”>
    <service
    name=”HelloService”
    targetNamespace=”urn:Star”
    typeNamespace=”urn:Star”
    packageName=”helloservice”>
    <interface name=”helloservice.HelloIF”/>
    </service>
    </configuration>
    该文件告诉wscompile创建model文件所需的信息
    ·服务名称:MyHelloService
    ·WSDL名字空间:urn:Star
    ·HelloService的所有类在包helloservice中
    ·服务的端点(endpoint)接口:helloservice.HelloIF
    C. 将服务打包为WAR文件
    WEB-INF/classes/hello/HelloIF.class
    WEB-INF/classes/hello/HelloImpl.class
    WEB-INF/jaxrpc-ri.xml
    WEB-INF/model.gz
    WEB-INF/web.xml
    jaxrpc-ri.xml文件如下所述
    <?xml version=”1.0” encoding=”UTF-8”?>
    <webServices xmlns=”http://java.sun.com/xml/ns/jax-rpc/ri/dd”
    version=”1.0”
    targetNamespaceBase=”urn:Star”
    typeNamespaceBase=”urn:Star”
    urlPatternBase=”webservice”>
    <endpoint name=”Hello”
    displayName=”HelloWorld Service”
    description=”A simple web service”
    interface=”helloservice.HelloIF”
    model=”/WEB-INF/model.gz”
    implementation=”helloservice.HelloImpl”/>
    <endpointMapping endpointName=”Hello” urlPattern=”/hello”/>
    </webServices>
    D. 处理WAR文件
    使用命令行
    wsdeploy -o hello-jaxrpc.war hello-jaxrpc-original.war
    wsdeploy工具完成以下几个任务
    ·读 hello-jaxrpc-original.war作为输入
    ·从jaxrpc-ri.xml文件中获得信息
    ·为服务生成tie classes
    ·生成名为HelloService.wsdl的WSDL文件
    ·将tie classes和HelloService.wsdl文件打包到新的war文件中
    E. 在服务器上部署服务
    如果你使用的是TOMCAT,你可以将WAR文件拷贝到webapps目录下,然后可以在
    <http://localhost:8080/[context]/[servicename>上看是否配置成功
    ·如何使用JAX-RPC创建Web Service客户端
    通常有三种类型的客户:Static Stub、Dynamic Proxy和Dynamic Invocation Interface(DII)
    Static Stub客户
    ·生成Stub
    通过使用config-wsdl.xml和wscompile工具,可以生成stub
    wscompile -gen:client -d build -classpath build config-wsdl.xml
    config-wsdl.xml文件如下
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
    <wsdl location="http://localhost:8080/helloWS/hello?WSDL"
    packageName="staticstub"/>
    </configuration>
    wscompile工具读取服务器上的WSDL文件并生成stub
    ·编写静态客户代码
    Stub stub=(Stub)(new HelloService_Impl().getHelloIFPort());
    HelloIF hello=(HelloIF)stub;
    Hello.sayHello(“starchu”);
    注意:HelloService_Impl类由wscompile生成
    ·编译代码
    ·运行客户端(需要包含saaj API和JAX-RPC API运行)
    Dynamic Proxy客户
    ·生成接口
    通过使用config-wsdl.xml文件和wscompile工具,可以生成客户所需的接口
    wscompile -import -d build -classpath build config-wsdl.xml
    config-wsdl.xml和前面列出的文件内容相同。
    ·编写动态客户代码
    ServiceFactory factory=ServiceFactory.newInstance();
    URL wsdlUrl=new URL(“<your web service wsdl url>”);
    Service service=factory.createService(wsdlUrl,
    new QName(“urn:Star”,”HelloService”));
    HelloIF hello=(HelloIF)service.getPort(
    new QName(“urn:Star”,”HelloIFPort”),HelloIF.class);
    Hello.sayHello(“starchu”);
    注意:这里不再需要静态客户代码的HelloService_Impl类
    ·编译代码
    ·运行客户端(需要包含saaj API和JAX-RPC API运行)
    Dynamic Invocation Interface客户
    这个方法为我们提供了更有弹性的客户调用方式,客户代码不在需要由wscompile工具生成的运行时类,当然这种代码更加复杂。具体步骤如下:
    ·创建ServiceFactory实例
    ServiceFactory factory=ServiceFactory.newInstance();

    ·创建Service(利用服务名的Qname)
    Service service=factory.createService(new QName(“HelloService”));

    ·创建Call对象(使用端点接口的Qname)
    Call call=service.createCall(new QName(“HelloIF”));

    ·设置端点的地址和一些Call对象属性
    call.setTargetEndpointAddress(args[0]);
    call.setProperty(Call.SOAPACTION_USE_PROPERTY,new Boolean(true));
    call.setProperty(Call.SOAPACTION_URI_PROPERTY,””);
    call.setProperty(“javax.xml.rpc.encodingstyle.namespace.uri”,
    “http://schemas.xmlsoap.org/soap/encoding/”);

    ·设置远程调用的返回类型、操作名和参数
    QName stringType=new Qname(“http://www.w3.org/2001/XMLSchema”,”string”)
    call.setReturnType(stringType);
    call.setOperationName(new Qname(“urn:Star”,”sayHello”));
    call.addParameter(“String_1”,stringType,ParameterMode.IN);

    ·调用call的invoke方法
    String [] param={ “ starchu “ };
    String retValue=call.invoke(param);

    ·编译代码并对Main方法设置<http://localhost:8080/helloWS/hello参数(服务器需有效>)

    3.SOAP Message Handler的例子
    通常使用JAX-RPC建立的Web Service并不需要开发人员自己处理SOAP消息,但是JAX-RPC提供了一种机制可以使程序员获得这种处理能力,这就是所谓的消息处理器。总的来说,像日志和加解密功能可以通过SOAP消息处理器实现,除此之外,你根本不需要处理SOAP消息。
    ·基本Handler处理过程
    SOAP请求
    ·客户端处理器在请求消息发送到服务器前被调用
    ·服务端处理器在请求消息分发到端点前被调用
    SOAP应答
    ·服务端处理器在应答消息发送回客户前被调用
    ·客户端处理器在应答消息转换成Java方法返回前被调用
    SOAP错误
    处理过程与SOAP应答的方式一样
    注意:处理器可以在任意端组成处理器链
    A.Handler基本编程模型
    服务端
    ·编写服务端点接口代码、实现服务并且实现服务端处理器类
    ·创建jaxrpc-ri.xml文件,以便wscompile使用,其中包含了Handler的信息
    ·创建web.xml文件
    ·编译所有代码
    ·将文件打包为WAR文件
    ·用wsdeploy工具将原始war文件替换为完整可部署的war文件
    ·在服务器上部署war文件
    客户端
    ·编写客户程序以及客户端处理器代码
    ·创建config.xml文件以便wscompile使用,它包含了客户端处理器的信息
    ·编译代码
    ·运行wscompile生成服务端点接口和客户类
    ·编译所有代码,并运行客户应用

    B.建立客户端处理器
    处理器必须扩展javax.xml.rpc.handler.GenericHandler类并且提供至少两个方法的实现init和getHandlers。此外,你可以利用handleXXX方法处理请求、应答和错误SOAP消息。基本步骤如下
    ·编写客户端处理器代码
    Public class ClientHandler extends GenericHandler{
    Public void init(HandlerInfo info){
    This.info=info;
    }
    public QName[] getHeaders(){
    return info.getHeaders();
    }
    public boolean handleRequest(MessageContext context){
    SOAPMessageContext smc=(SOAPMessageContext)context;
    SOAPMessage message=smc.getMessage();
    file://You can use SOAP API to implement your own logic
    file://such as logging and encrypt
    ……
    file://Set a logger element in the SOAPHeader
    SOAPHeaderElement loggerElement=
    header.addHeaderElement(envelope.createName(“loginfo”,
    “ns1”,”urn:Star:headprops”));
    loggerElement.setMustUnderstand(true);
    loggerElement.setValue(“10”);
    file://Set a name element in the SOAP Header
    SOAPHeaderElement nameElement=
    Header.addHeaderElement(envelope.createName(“client”,
    “ns1”,”urn:Star:headprops”));
    nameElement.addTextNode(“Star chu”);
    }
    }
    ·编辑config.xml文件
    <?xml version=”1.0” encoding=”UTF-8”?>
    <configuration xmlns=”http://java.sun.com/xml/ns/jax-rpc/ri/config”?>
    <wsdl location=”http://localhost:8080/handlerWS/handler?WSDL
    packageName=”client”>
    <handlerChains>
    <chain runAt=”client”>
    <handler className=”client.ClientHandler”>
    <property name=”name” value=”client handler”/>
    </handler>
    </chain>
    </handlerChains></wsdl></configuration>
    ·编写静态客户
    C.建立服务端处理器
    ·编写服务端处理器(与客户端结构类似)
    Public boolean handleRequest(MessageContext context){
    SOAPMessageContext smc=(SOAPMessageContext)context;
    ……
    Iterator it=header.examineAllHeaderElements();
    While(it.hasNext()){
    SOAPElement element=(SOAPElement)it.next();
    If(element name is loginfo and must understand it){
    element.getValue();
    element.detach();
    file://Invoke only when the setMustUnderstand(true)
    }
    }
    }
    detach方法用来移除元素,这个需求仅当一个元素设置了mustUnderstand属性在必要。
    ·编辑jaxrpc-ri.xml文件
    <?xml version=”1.0” encoding=”UTF-8”?>
    <webServices xmlns=”http://java.sun.com/jax-rpc/config/ri/dd”
    version=”1.0”
    targetNamespaceBase=”urn:Star:wsdl”
    typeNamespaceBase=”urn:Star:types”
    urlPatternBase=”/handler”>
    <endpoint name=”HandlerTest”
    displayName=”Handler Test”
    description=” … …”
    interface=”service.HandlerTest”
    model=”/WEB-INF/model.gz”
    implementation=”service.HandlerTestImpl”>
    <handlerChains>
    <chain runAt=”server”>
    <handler className=”service.LoggerHandler”
    headers=”ns1:loginfo”
    xmlns:ns1=”urn:Star:headerprops”>
    <property name=”name” value=”Logger”/>
    </handler>
    <handler className=”service.NameHandler”>
    <propery name=”name” value=”Name”/>
    </handler>
    </chain>
    </handlerChains>
    </endpoint>
    <endpointMapping endpointName=”HandlerTest”
    urlPattern=”/handler”/>
    </webServices>
    在第一个处理器中,XML使用了属性 headers描述头信息。这是因为客户代码告诉服务端,logger头必须被理解,否则客户将收到SOAP错误消息
    ·生成WAR文件并部署到服务器上
    4.源代码
    ·HelloIF.java(endpoint接口)
    package helloservice;

    import java.rmi.RemoteException;
    import java.rmi.Remote;

    public interface HelloIF extends Remote{
    public String sayHello(String target) throws RemoteException;
    }
    ·HelloImpl.java
    package helloservice;

    public class HelloImpl implements HelloIF{
    private String message="Hello";
    public String sayHello(String target){
    return message+target;
    }
    }
    ·StaticClient.java
    package staticstub;
    import javax.xml.rpc.Stub;
    public class StaticClient{
    private static String endpointAddress;
    public static void main(String [] args){
    if(args.length!=1){
    System.err.println("Usage : java HelloClient [endpoint address]");
    System.exit(-1);
    }
    endpointAddress=args[0];
    System.out.println("Connect to :"+endpointAddress);
    try{
    Stub stub=createStub();
    stub._setProperty(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY,
    endpointAddress);
    HelloIF hello=(HelloIF)stub;
    System.out.println(hello.sayHello(" Starchu!"));
    }catch(Exception e){System.err.println(e.toString());}
    }
    private static Stub createStub(){
    return (Stub)(new HelloService_Impl().getHelloIFPort());
    }
    }
    ·DynamicClient.java
    package dynamicproxy;
    import java.net.URL;
    import javax.xml.namespace.QName;
    import javax.xml.rpc.Service;
    import javax.xml.rpc.ServiceFactory;
    import javax.xml.rpc.JAXRPCException;
    import staticstub.HelloIF;

    public class DynamicClient{
    private static String wsdl;
    private static String namespaceUri="urn:Star:wsdl";
    private static String serviceName="HandlerService";
    private static String portName="HandlerTestPort";

    public static void main(String [] args){
    if(args.length!=1){
    System.err.println("Usage : java DynamicClient [server Url]");
    System.exit(-1);
    }
    System.out.println("Connect to :"+args[0]);
    helloWsdl=args[0]+"?WSDL";
    try{
    URL wsdlUrl=new URL(wsdl);
    ServiceFactory serviceFactory=ServiceFactory.newInstance();
    Service service=
    serviceFactory.createService(wsdlUrl,
    new QName(namespaceUri,serviceName));
    HandlerTest proxy=(HandlerTest)service.getPort(
    new QName(namespaceUri,portName),HandlerTest.class);
    proxy.test();
    }catch(Exception e){
    System.err.println(e.toString());
    }
    }
    }
    ·DIIClient.java
    package dii;
    import javax.xml.rpc.*;
    import javax.xml.namespace.*;

    public class DIIClient{
    private static String qnameService = "HelloService";
    private static String qnamePort = "HelloIF";
    private static String BODY_NAMESPACE_VALUE ="urn:Star";
    private static String ENCODING_STYLE_PROPERTY ="javax.xml.rpc.encodingstyle.namespace.uri";
    private static String NS_XSD ="http://www.w3.org/2001/XMLSchema";
    private static String URI_ENCODING ="http://schemas.xmlsoap.org/soap/encoding/";

    public static void main(String [] args){
    try{

    ServiceFactory factory=ServiceFactory.newInstance();
    Service service=factory.createService(new QName(qnameService));
    QName port=new QName(qnamePort);
    Call call=service.createCall(port);
    call.setTargetEndpointAddress(args[0]);
    call.setProperty(Call.SOAPACTION_USE_PROPERTY,new Boolean(true));
    call.setProperty(Call.SOAPACTION_URI_PROPERTY,"");
    call.setProperty(ENCODING_STYLE_PROPERTY,URI_ENCODING);
    QName qnameTypeString=new QName(NS_XSD,"string");
    call.setReturnType(qnameTypeString);
    call.setOperationName(new QName(BODY_NAMESPACE_VALUE,"sayHello"));
    call.addParameter("String_1",qnameTypeString,ParameterMode.IN);
    String [] params = { "Starchu" };
    System.out.println((String)call.invoke(params));
    }catch(Exception e){
    System.err.println(e.toString());
    }
    }
    }
    ·Ant文件build.xml
    <project name="helloWS" basedir="." default="deploy">
    <property file="build.properties"/>
    <property name="build" value="build"/>
    <property name="dist" value="${build}\classes"/>
    <property name="lib" value="${build}\lib"/>
    <property name="src" value="src"/>
    <property name="etc" value="${src}\etc"/>

    <target name="clean">
    <delete dir="${build}"/>
    </target>

    <target name="init">
    <mkdir dir="${build}"/>
    <mkdir dir="${dist}"/>
    <mkdir dir="${lib}"/>
    </target>

    <path id="classpath">
    <fileset dir="${tomcat.home}">
    <include name="jaxrpc/**/*.jar"/>
    <include name="jaxb/**/*.jar"/>
    <include name="jaxp/**/*.jar"/>
    <include name="saaj/**/*.jar"/>
    <include name="jwsdp-shared/lib/**/*.jar"/>
    </fileset>
    <pathelement path="${dist}"/>
    <pathelement location="${lib}"/>
    </path>

    <target name="compile-service" depends="init">
    <javac srcdir="${src}" destdir="${dist}" includes="HelloService/**/*.java"/>
    </target>

    <target name="generate-sei-service" depends="compile-service">
    <exec executable="wscompile.bat">
    <arg line="-define -d ${build} -nd ${build} -classpath ${dist} ${etc}\config-interface.xml -model ${build}\model.gz"/>
    </exec>
    <copy todir="${build}">
    <fileset dir="${etc}" includes="*.xml"/>
    </copy>
    </target>

    <target name="package-service" depends="generate-sei-service">
    <war warfile="${build}\${ant.project.name}-portable.war"
    webxml="${build}\web.xml">
    <webinf dir="${build}" includes="*.xml,*.gz,*.wsdl" excludes="web.xml"/>
    <classes dir="${dist}" includes="**/*.class" defaultexcludes="no"/>
    </war>
    </target>

    <target name="process-war" depends="package-service">
    <exec executable="wsdeploy.bat">
    <arg line="-o ${ant.project.name}.war ${build}\${ant.project.name}-portable.war"/>
    </exec>
    </target>

    <target name="deploy">
    <copy file="${ant.project.name}.war" todir="${server}"/>
    </target>

    <target name="undeploy">
    <delete file="${server}\${ant.project.name}.war"/>
    </target>

    <!-- Generating Static Client -->
    <target name="generate-stubs" depends="init">
    <exec executable="wscompile.bat">
    <arg line="-gen:client -d ${dist} -classpath ${dist} ${etc}\config-wsdl.xml"/>
    </exec>
    </target>

    <target name="compile-client" depends="generate-stubs">
    <javac srcdir="${src}" destdir="${dist}" includes="StaticStub/**/*.java">
    <classpath refid="classpath"/>
    </javac>
    </target>

    <target name="package-client" depends="compile-client">
    <jar destfile="${lib}\client.jar" basedir="${dist}" excludes="HelloService/**/*.class"/>
    </target>

    <target name="run-client" depends="package-client">
    <java classname="staticstub.HelloClient"
    classpathref="classpath"
    fork="true">
    <sysproperty key="endpoint" value="${endpoint}"/>
    <arg value="${server.port.url}"/>
    </java>
    </target>

    <!-- Generating Dynamic Client -->
    <target name="generate-interface" depends="init">
    <exec executable="wscompile.bat">
    <arg line="-import -d ${dist} -classpath ${dist} ${etc}\config-wsdl.xml"/>
    </exec>
    </target>

    <target name="compile-dynamic-client" depends="generate-interface">
    <javac srcdir="${src}" destdir="${dist}" includes="DynamicProxy/**/*.java">
    <classpath refid="classpath"/>
    </javac>
    </target>

    <target name="package-dynamic-client" depends="compile-dynamic-client">
    <jar destfile="${lib}\client.jar" basedir="${dist}" includes="**/HelloIF.class,**/DynamicClient.class"/>
    </target>

    <target name="run-dynamic-client" depends="package-dynamic-client">
    <java classname="dynamicproxy.DynamicClient"
    classpathref="classpath"
    fork="true">
    <sysproperty key="endpoint" value="${endpoint}"/>
    <arg value="${server.port.url}"/>
    </java>
    </target>

    <!-- Generating Dynamic Invocation Interface -->

    <target name="compile-dii">
    <javac srcdir="${src}" destdir="${dist}" includes="DII/**/*.java">
    <classpath refid="classpath"/>
    </javac>
    </target>

    <target name="run-dii" depends="compile-dii">
    <java classname="dii.DIIClient"
    classpathref="classpath"
    fork="true">
    <sysproperty key="endpoint" value="${endpoint}"/>
    <arg value="${server.port.url}"/>
    </java>
    </target>

    </project>
    ·属性文件(build.xml文件使用)
    server=C:/Java/jwsdp-1.2/webapps
    tomcat.home=C:/Java/jwsdp-1.2
    endpoint=http://localhost:8080/helloWS/hello
    server.port.url=http://localhost:8080/helloWS/hello

    参考资料
    1. Developing Web Service Series <http://www.theserverside.com/resources/article.jsp?l=Systinet-web-services-part-1> www.theserverside.com
    2. JWSDP-1.2 Tutorial java.sun.com

    posted @ 2007-05-18 15:24 鸿雁| 编辑 收藏