本作代码下载:https://files.cnblogs.com/files/xiandedanteng/LeftInnerNotExist20191222.rar

人们总是喜欢给出或是得到一个简单明了甚至有些粗暴的结论,但现实之复杂往往不是几句简单的话语所能描述。(版权所有)

下结论前,先让我们看一个对比实验。

有一张表delivery_history,表结构如下:

  1. CREATE TABLE delivery_history
  2. (
  3. id ,) not null primary key,
  4. name NVARCHAR2() not null,
  5. order_no ,) not null ,
  6. shipper_code ,) not null ,
  7. createtime ) not null
  8. )

在这张表里,前面两个字段可以忽略,重要的是order_no,shipper_code,createtime三个字段,order_no代表订单号,shipper_code代表运输者代号,createtime是这条记录创建的时间戳,而我们的主要任务是快速找出order_no和shipper_code相同时,createtime最近的那条记录。delivery_history表中目前有五十万条数据,往后可能更多,因此多要对SQL的执行效率多加考虑。(往下的实验中,考虑到在一张表上反复试验太耗时,也不利于数据的存留,表名会加上数字编号后缀,大家知道它们都是delivery_history表的替身就好。)

为此任务,我书写了下面三种SQL:

方案一:左连接方案
  1. SELECT
  2. DH1.ORDER_NO,
  3. DH1.SHIPPER_CODE
  4. from
  5. delivery_history DH1
  6. left JOIN delivery_history DH2 on
  7. DH1.SHIPPER_CODE = DH2.SHIPPER_CODE
  8. and DH1.ORDER_NO = DH2.ORDER_NO
  9. and DH2.createtime > DH1.createtime
  10. where DH2.createtime IS NULL
方案二:groupby内连接方案
  1. select
  2. DH1.ORDER_NO,
  3. DH1.SHIPPER_CODE
  4. from
  5. delivery_history dh1 ,
  6. (select SHIPPER_CODE,ORDER_NO,max(createtime) as utime from delivery_history
  7. group by SHIPPER_CODE,ORDER_NO) dh2
  8. where
  9. dh1.SHIPPER_CODE=dh2.SHIPPER_CODE and
  10. dh1.ORDER_NO=dh2.ORDER_NO and
  11. dh1.createtime=dh2.utime
方案三:not exist方案
  1. select
  2. a.ORDER_NO,
  3. a.SHIPPER_CODE
  4. from delivery_history a
  5.  
  6. from delivery_history b
  7. where b.SHIPPER_CODE=a.SHIPPER_CODE and
  8. b.ORDER_NO=a.ORDER_NO and
  9. b.createtime>a.createtime)

经过仔细比对,这三种方案均能完成任务,那么哪种速度最快呢?

在给出最终结论之前,让我们来看看数据的情况:

上图中,我已经用红框将数据分组了,可以观察得知,基本上是一条记录对应order_no和shipper_code都相同的一组,跨多条记录的组仅有三组。

对这样的数据,三种方案谁更强呢?

性能测试结果如下:

  1. ::, INFO[main]-Compare query in table'delivery_history01'.
  2. ::, INFO records.
  3. ::, INFO records.
  4. ::, INFO records.
  5. ::, INFO[main]-There are same elements in leftMap and innerMap.
  6. ::, INFO[main]-There are same elements in leftMap and notExistMap.

说明一下,deliery_history表中有五十万条数据,分成了389755组,leftMap,innerMap,notExistMap存的都是组内时间最靠近现在的记录。

对比表格如下:

左连接 groupby内连接 not exist方式
5s431ms 6s648ms 6s211ms

可以看出,在基本是一条记录对应一组的情况下,左连接胜出。

从这里我们可以得出结论,如果数据分组很小,导致主从表记录数差别不大时,左连接是最快的。

再造一次数据,这回力图减少一条记录对一组的情况:

可以看出,包含多条数据的组越来越多了。

再测试一下:

  1. ::, INFO[main]-Compare query in table'delivery_history02'.
  2. ::, INFO records.
  3. ::, INFO records.
  4. ::, INFO records.
  5. ::, INFO[main]-There are same elements in leftMap and innerMap.
  6. ::, INFO[main]-There are same elements in leftMap and notExistMap.

这回,五十万条记录分了19万2062组,组内扩大了,分组减少了。

对比表格如下:

左连接 groupby内连接 not exist
4s134ms 3s644ms 4s184ms

这回的胜出者是内连接方案,它相对另外两种有数百毫秒的优势。

下面我让组内部扩大些,同时分组数就更少了。

这把1-44条记录为第一组,45-83位第二组,组内扩大很多。

测试结果:

  1. ::, INFO[main]-Compare query in table'delivery_history03'.
  2. ::, INFO records.
  3. ::, INFO records.
  4. ::, INFO records.
  5. ::, INFO[main]-There are same elements in leftMap and innerMap.
  6. ::, INFO[main]-There are same elements in leftMap and notExistMap.

这次五十万记录缩成了一万五千四百多组,而内联方案再次胜出,领先优势接近一倍,not exist方案也开始超越左连接。

左连接 groupby内连接 not exist
5s434ms 2s979ms 4s479ms

此时我们可以推断出,随着组的扩大,内联方案中经过group by后的从表的规模急剧缩小,再与主表连接后结果集就比其它方案数据少,因此而胜出了。

让我们再次扩大组以验证这个理论。

这一把近一百条都归到一组内,结果还会是内联方案胜出吗?

  1. ::, INFO[main]-Compare query in table'delivery_history'.
  2. ::, INFO records.
  3. ::, INFO records.
  4. ::, INFO records.
  5. ::, INFO[main]-There are same elements in leftMap and innerMap.
  6. ::, INFO[main]-There are same elements in leftMap and notExistMap.

上面的理论是对的,组越大,group by后的从表就越小,因而形成的结果集就小,内联的领跑优势越来越明显,而not exist方式也与第三名拉开了差距。

左连接 groupby内连接 not exist
10s190ms 2s80ms 7s709ms

是不是内联就稳了呢?不着急下结论,让我们增加shipper看看。

这种情况下,order_no相同,但shipper不同的情况增加了,组被进一步细化。

测试结果:

  1. ::, INFO[main]-Compare query in table'delivery_history04'.
  2. ::, INFO records.
  3. ::, INFO records.
  4. ::, INFO records.
  5. ::, INFO[main]-There are same elements in leftMap and innerMap.
  6. ::, INFO[main]-There are same elements in leftMap and notExistMap.

你可以发现,左连接方案依然落后,但前两名差距不大了。

左连接 groupby内连接 not exist
4s744ms 3s186ms 3s637ms

我们发现在增加shipper情况下,not exist方案开始跟上了!

我再次增加shipper,终于让notExist方案成为第一名:

  1. ::, INFO[main]-Compare query in table'delivery_history06'.
  2. ::, INFO records.
  3. ::, INFO records.
  4. ::, INFO records.
  5. ::, INFO[main]-There are same elements in leftMap and innerMap.
  6. ::, INFO[main]-There are same elements in leftMap and notExistMap.
左连接 groupby内连接 not exist
4s169ms 3s425ms 3s206ms

再次增加十个shipper,notExist方案优势就出来了:

  1. ::, INFO[main]-Compare query in table'delivery_history07'.
  2. ::, INFO records.
  3. ::, INFO records.
  4. ::, INFO records.
  5. ::, INFO[main]-There are same elements in leftMap and innerMap.
  6. ::, INFO[main]-There are same elements in leftMap and notExistMap.
左连接 groupby内连接 not exist
4s144ms 4s615ms 3s994ms

依照上面的实验,我们可以得出以下结论:

数据零散不成组时,左连接最快;

数据按order_no分组越大,shipper数量不多时,groupby内连接方案最快;

shipper数量越多,not exist方案优势越大。

从这些实验可以看出来,不同的数据,会导致不同的方案胜出;或者说,没有最快的sql方案,只有最适配数据的方案。

没想到吧,SQL优化工作最后成了数据分析工作。

以下是用到的代码:

关于建表的代码:

  1. package com.hy;
  2.  
  3. import java.sql.Connection;
  4. import java.sql.DriverManager;
  5. import java.sql.ResultSet;
  6. import java.sql.SQLException;
  7. import java.sql.Statement;
  8.  
  9. import org.apache.log4j.Logger;
  10.  
  11. // Used to create a table in oracle
  12. public class TableCreater {
  13. private static Logger log = Logger.getLogger(TableCreater.class);
  14. private final String table="delivery_history07";
  15.  
  16. public boolean createTable() {
  17. Connection conn = null;
  18. Statement stmt = null;
  19.  
  20. try{
  21. Class.forName(DBParam.Driver).newInstance();
  22. conn = DriverManager.getConnection(DBParam.DbUrl, DBParam.User, DBParam.Pswd);
  23. stmt = conn.createStatement();
  24.  
  25. String createTableSql=getCreateTbSql(table);
  26. stmt.execute(createTableSql);
  27.  
  28. if(isTableExist(table,stmt)==true) {
  29. log.info("Table:'"+table+"' created.");
  30. return true;
  31. }
  32.  
  33. } catch (Exception e) {
  34. System.out.print(e.getMessage());
  35. } finally {
  36. try {
  37. stmt.close();
  38. conn.close();
  39. } catch (SQLException e) {
  40. System.out.print("Can't close stmt/conn because of " + e.getMessage());
  41. }
  42. }
  43.  
  44. return false;
  45. }
  46.  
  47. /**
  48. * Get a table's ddl
  49. * @param table
  50. * @return
  51. */
  52. private String getCreateTbSql(String table) {
  53. StringBuilder sb=new StringBuilder();
  54. sb.append("CREATE TABLE "+table);
  55. sb.append("(");
  56. sb.append("id NUMBER(8,0) not null primary key,");
  57. sb.append("name NVARCHAR2(60) not null,");
  58. sb.append("order_no NUMBER(10,0) DEFAULT 0 not null ,");
  59. sb.append("shipper_code NUMBER(10,0) DEFAULT 0 not null ,");
  60. sb.append("createtime TIMESTAMP (6) not null");
  61. sb.append(")");
  62.  
  63. return sb.toString();
  64. }
  65.  
  66. // Execute a sql
  67. //private int executeSql(String sql,Statement stmt) throws SQLException {
  68. // return stmt.executeUpdate(sql);
  69. //}
  70.  
  71. // If a table exists
  72. private boolean isTableExist(String table,Statement stmt) throws SQLException {
  73. String sql="SELECT COUNT (*) as cnt FROM ALL_TABLES WHERE table_name = UPPER('"+table+"')";
  74.  
  75. ResultSet rs = stmt.executeQuery(sql);
  76.  
  77. while (rs.next()) {
  78. int count = rs.getInt("cnt");
  79. return count==1;
  80. }
  81.  
  82. return false;
  83. }
  84.  
  85. // Entry point
  86. public static void main(String[] args) {
  87. TableCreater tc=new TableCreater();
  88. tc.createTable();
  89. }
  90. }

用于创建数据的代码:

  1. package com.hy;
  2.  
  3. import java.sql.Connection;
  4. import java.sql.DriverManager;
  5. import java.sql.SQLException;
  6. import java.sql.Statement;
  7. import java.text.MessageFormat;
  8. import java.util.ArrayList;
  9. import java.util.List;
  10. import java.util.Random;
  11.  
  12. import org.apache.log4j.Logger;
  13.  
  14. // Used to insert ten thousands of records to table 'delivery_hisotry'
  15. public class TableRecordInserter {
  16. private static Logger log = Logger.getLogger(TableCreater.class);
  17.  
  18. private final String Table="delivery_history07";
  19. private final int Total=500000;
  20.  
  21. public boolean fillTable() {
  22. Connection conn = null;
  23. Statement stmt = null;
  24.  
  25. try{
  26. Class.forName(DBParam.Driver).newInstance();
  27. conn = DriverManager.getConnection(DBParam.DbUrl, DBParam.User, DBParam.Pswd);
  28. conn.setAutoCommit(false);
  29. stmt = conn.createStatement();
  30.  
  31. long startMs = System.currentTimeMillis();
  32. clearTable(stmt,conn);
  33. List<String> insertSqls=generateInsertSqlList();
  34. betachInsert(insertSqls,stmt,conn);
  35. long endMs = System.currentTimeMillis();
  36. log.info("It takes "+ms2DHMS(startMs,endMs)+" to fill "+Total+" records.");
  37. } catch (Exception e) {
  38. e.printStackTrace();
  39. } finally {
  40. try {
  41. stmt.close();
  42. conn.close();
  43. } catch (SQLException e) {
  44. System.out.print("Can't close stmt/conn because of " + e.getMessage());
  45. }
  46. }
  47.  
  48. return false;
  49. }
  50.  
  51. private void clearTable(Statement stmt,Connection conn) throws SQLException {
  52. stmt.executeUpdate("truncate table "+Table);
  53. conn.commit();
  54. log.info("Cleared table:'"+Table+"'.");
  55. }
  56.  
  57. private int betachInsert(List<String> insertSqls,Statement stmt,Connection conn) throws SQLException {
  58. int inserted=0;
  59. final int BatchSize=250;
  60. int count=insertSqls.size();
  61. int index=0;
  62. int times=count/BatchSize;
  63. for(int i=0;i<times;i++) {
  64. StringBuilder sb=new StringBuilder();
  65. sb.append("INSERT ALL ");
  66.  
  67. for(int j=0;j<BatchSize;j++) {
  68. index=i*BatchSize+j;
  69. sb.append(insertSqls.get(index));
  70. }
  71.  
  72. sb.append(" select * from dual");
  73. String sql = sb.toString();
  74.  
  75. int n=stmt.executeUpdate(sql);
  76. inserted+=n;
  77. conn.commit();
  78.  
  79. log.info("#"+i+" inserted " +n+" records.");
  80. }
  81.  
  82. return inserted;
  83. }
  84.  
  85. private List<String> generateInsertSqlList() {
  86. List<String> sqlList=new ArrayList<String>();
  87. int index=0;
  88. do {
  89. int orderNoRange=getRandom(1,100);// 调整order_no,L89
  90. int orderNo=index*1000+orderNoRange;
  91. for(int i=0;i<orderNoRange;i++) {
  92. int shipper_code=getShipperCode();
  93.  
  94. String insertSql=getInsertSql(index,orderNo,shipper_code);
  95. sqlList.add(insertSql);
  96.  
  97. index++;
  98. }
  99. }while(index<Total);
  100.  
  101. log.info("generated "+sqlList.size()+" insert sqls.");
  102.  
  103. return sqlList;
  104. }
  105.  
  106. // get partial insert sql
  107. private String getInsertSql(int id,int orderNo,int shipperCode) {
  108. String raw=" INTO {0}(id,name, order_no,shipper_code,createtime) values(''{1}'',''{2}'',''{3}'',''{4}'',sysdate) ";
  109.  
  110. String ids=String.valueOf(id);
  111. String name="N_"+ids;
  112.  
  113. Object[] arr={Table,ids,name,String.valueOf(orderNo),String.valueOf(shipperCode)};
  114.  
  115. return MessageFormat.format(raw, arr);
  116. }
  117.  
  118. // get a random shipper-code
  119. private int getShipperCode() {
  120. int[] arr= {1111,2222,3333,4444,5555,6666,7777,8888,9999,1010,2020,3030,4040,5050,6060,7070,8080,9090,1011,2022,3033,4044,5055,6066,7077,8088,9099,1811,2822,3833,4844,5855,6866,7877,8888,9899};// 调整shipper_code,L120
  121. int seed=getRandom(0,arr.length-1);
  122. return arr[seed];
  123. }
  124.  
  125. // get a random integer between min and max
  126. public static int getRandom(int min, int max){
  127. Random random = new Random();
  128. int rnd = random.nextInt(max) % (max - min + 1) + min;
  129. return rnd;
  130. }
  131.  
  132. // change seconds to DayHourMinuteSecond format
  133. private static String ms2DHMS(long startMs, long endMs) {
  134. String retval = null;
  135. long secondCount = (endMs - startMs) / 1000;
  136. String ms = (endMs - startMs) % 1000 + "ms";
  137.  
  138. long days = secondCount / (60 * 60 * 24);
  139. long hours = (secondCount % (60 * 60 * 24)) / (60 * 60);
  140. long minutes = (secondCount % (60 * 60)) / 60;
  141. long seconds = secondCount % 60;
  142.  
  143. if (days > 0) {
  144. retval = days + "d" + hours + "h" + minutes + "m" + seconds + "s";
  145. } else if (hours > 0) {
  146. retval = hours + "h" + minutes + "m" + seconds + "s";
  147. } else if (minutes > 0) {
  148. retval = minutes + "m" + seconds + "s";
  149. } else {
  150. retval = seconds + "s";
  151. }
  152.  
  153. return retval + ms;
  154. }
  155.  
  156. // Entry point
  157. public static void main(String[] args) {
  158. TableRecordInserter tri=new TableRecordInserter();
  159. tri.fillTable();
  160. }
  161. }

用于比较的代码:

  1. package com.hy;
  2.  
  3. import java.security.MessageDigest;
  4. import java.sql.Connection;
  5. import java.sql.DriverManager;
  6. import java.sql.ResultSet;
  7. import java.sql.SQLException;
  8. import java.sql.Statement;
  9. import java.util.ArrayList;
  10. import java.util.HashMap;
  11. import java.util.List;
  12. import java.util.Map;
  13.  
  14. import org.apache.log4j.Logger;
  15.  
  16. //Used for hold columns
  17. class DhItem {
  18. String order_no;
  19. String shipper_code;
  20.  
  21. public String toString() {
  22. List<String> ls = new ArrayList<String>();
  23.  
  24. ls.add(order_no);
  25. ls.add(shipper_code);
  26.  
  27. return String.join(",", ls);
  28. }
  29. }
  30.  
  31. public class Comparer {
  32. private static Logger log = Logger.getLogger(Comparer.class);
  33. private final String Table="delivery_history07";
  34.  
  35. // print three plan comparison
  36. public void printComparison() {
  37. Connection conn = null;
  38. Statement stmt = null;
  39.  
  40. try {
  41. Class.forName(DBParam.Driver).newInstance();
  42. conn = DriverManager.getConnection(DBParam.DbUrl, DBParam.User, DBParam.Pswd);
  43. stmt = conn.createStatement();
  44.  
  45. log.info("Compare query in table'"+Table+"'.");
  46.  
  47. long startMs = System.currentTimeMillis();
  48. Map<String,DhItem> leftMap=fetchMap(getLeftjoinSql(),stmt);
  49. long endMs = System.currentTimeMillis();
  50. log.info("It takes "+ms2DHMS(startMs,endMs)+" to run LeftjoinSql and fetch "+leftMap.size()+" records.");
  51.  
  52. startMs = System.currentTimeMillis();
  53. Map<String,DhItem> innerMap=fetchMap(getInnerSql(),stmt);
  54. endMs = System.currentTimeMillis();
  55. log.info("It takes "+ms2DHMS(startMs,endMs)+" to run innerJoinSql and fetch "+innerMap.size()+" records.");
  56.  
  57. startMs = System.currentTimeMillis();
  58. Map<String,DhItem> notExistMap=fetchMap(getNotExistSql(),stmt);
  59. endMs = System.currentTimeMillis();
  60. log.info("It takes "+ms2DHMS(startMs,endMs)+" to run notExistSql and fetch "+notExistMap.size()+" records.");
  61.  
  62. if(compare(leftMap,innerMap)==true) {
  63. log.info("There are same elements in leftMap and innerMap.");
  64. }
  65.  
  66. if(compare(leftMap,notExistMap)==true) {
  67. log.info("There are same elements in leftMap and notExistMap.");
  68. }
  69. } catch (Exception e) {
  70. e.printStackTrace();
  71. } finally {
  72. try {
  73. stmt.close();
  74. conn.close();
  75. } catch (SQLException e) {
  76. System.out.print("Can't close stmt/conn because of " + e.getMessage());
  77. }
  78. }
  79. }
  80.  
  81. // Compare the elements in two map
  82. private boolean compare(Map<String,DhItem> scrMap,Map<String,DhItem> destMap) {
  83. int count=0;
  84. for(String key:scrMap.keySet()) {
  85. if(destMap.containsKey(key)) {
  86. count++;
  87. }
  88. }
  89.  
  90. return count==scrMap.size() && count==destMap.size();
  91. }
  92.  
  93. private Map<String,DhItem> fetchMap(String sql,Statement stmt) throws SQLException {
  94.  
  95. Map<String,DhItem> map=new HashMap<String,DhItem>();
  96.  
  97. ResultSet rs = stmt.executeQuery(sql);
  98. while (rs.next()) {
  99. DhItem dhItem=new DhItem();
  100. dhItem.order_no=rs.getString("order_no");
  101. dhItem.shipper_code=rs.getString("shipper_code");
  102. map.put(toMD5(dhItem.toString()), dhItem);
  103. }
  104.  
  105. return map;
  106. }
  107.  
  108. // DH表自己和自己进行左连接方案(三种方案都是为了获得ORDER_NO,SHIPPER_CODE相同时创建时间最新的记录)
  109. private String getLeftjoinSql() {
  110. StringBuilder sb = new StringBuilder();
  111. sb.append(" SELECT ");
  112. sb.append(" DH1.ORDER_NO, ");
  113. sb.append(" DH1.SHIPPER_CODE ");
  114. sb.append(" from ");
  115. sb.append(" "+Table+" DH1 ");
  116. sb.append(" left JOIN "+Table+" DH2 on ");
  117. sb.append(" DH1.SHIPPER_CODE = DH2.SHIPPER_CODE ");
  118. sb.append(" and DH1.ORDER_NO = DH2.ORDER_NO ");
  119. sb.append(" and DH2.createtime > DH1.createtime ");
  120. sb.append(" where DH2.createtime IS NULL ");
  121. String sql = sb.toString();
  122. return sql;
  123. }
  124.  
  125. // DH表先自己分组方案(三种方案都是为了获得ORDER_NO,SHIPPER_CODE相同时创建时间最新的记录)
  126. private String getInnerSql() {
  127. StringBuilder sb = new StringBuilder();
  128. sb.append(" select ");
  129. sb.append(" DH1.ORDER_NO, ");
  130. sb.append(" DH1.SHIPPER_CODE ");
  131. sb.append(" from ");
  132. sb.append(" "+Table+" dh1 , ");
  133. sb.append(" (select SHIPPER_CODE,ORDER_NO,max(createtime) as utime from "+Table+" ");
  134. sb.append(" group by SHIPPER_CODE,ORDER_NO) dh2 ");
  135. sb.append(" where ");
  136. sb.append(" dh1.SHIPPER_CODE=dh2.SHIPPER_CODE and ");
  137. sb.append(" dh1.ORDER_NO=dh2.ORDER_NO and ");
  138. sb.append(" dh1.createtime=dh2.utime ");
  139.  
  140. String sql = sb.toString();
  141.  
  142. return sql;
  143. }
  144.  
  145. // ‘不存在’最新方案(三种方案都是为了获得ORDER_NO,SHIPPER_CODE相同时创建时间最新的记录)
  146. private String getNotExistSql() {
  147. StringBuilder sb = new StringBuilder();
  148. sb.append(" select ");
  149. sb.append(" a.ORDER_NO, ");
  150. sb.append(" a.SHIPPER_CODE ");
  151. sb.append(" from "+Table+" a ");
  152. sb.append(" where not exists( select 1 ");
  153. sb.append(" from "+Table+" b ");
  154. sb.append(" where b.SHIPPER_CODE=a.SHIPPER_CODE and ");
  155. sb.append(" b.ORDER_NO=a.ORDER_NO and ");
  156. sb.append(" b.createtime>a.createtime) ");
  157.  
  158. String sql = sb.toString();
  159. return sql;
  160. }
  161.  
  162. /**
  163. * change seconds to DayHourMinuteSecond format
  164. *
  165. * @param startMs
  166. * @param endMs
  167. * @return
  168. */
  169. private static String ms2DHMS(long startMs, long endMs) {
  170. String retval = null;
  171. long secondCount = (endMs - startMs) / 1000;
  172. String ms = (endMs - startMs) % 1000 + "ms";
  173.  
  174. long days = secondCount / (60 * 60 * 24);
  175. long hours = (secondCount % (60 * 60 * 24)) / (60 * 60);
  176. long minutes = (secondCount % (60 * 60)) / 60;
  177. long seconds = secondCount % 60;
  178.  
  179. if (days > 0) {
  180. retval = days + "d" + hours + "h" + minutes + "m" + seconds + "s";
  181. } else if (hours > 0) {
  182. retval = hours + "h" + minutes + "m" + seconds + "s";
  183. } else if (minutes > 0) {
  184. retval = minutes + "m" + seconds + "s";
  185. } else {
  186. retval = seconds + "s";
  187. }
  188.  
  189. return retval + ms;
  190. }
  191.  
  192. public static String toMD5(String key) {
  193. char hexDigits[] = {
  194. '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
  195. };
  196. try {
  197. byte[] btInput = key.getBytes();
  198. // 获得MD5摘要算法的 MessageDigest 对象
  199. MessageDigest mdInst = MessageDigest.getInstance("MD5");
  200. // 使用指定的字节更新摘要
  201. mdInst.update(btInput);
  202. // 获得密文
  203. byte[] md = mdInst.digest();
  204. // 把密文转换成十六进制的字符串形式
  205. int j = md.length;
  206. char str[] = new char[j * 2];
  207. int k = 0;
  208. for (int i = 0; i < j; i++) {
  209. byte byte0 = md[i];
  210. str[k++] = hexDigits[byte0 >>> 4 & 0xf];
  211. str[k++] = hexDigits[byte0 & 0xf];
  212. }
  213. return new String(str);
  214. } catch (Exception e) {
  215. return null;
  216. }
  217. }
  218.  
  219. public static void main(String[] args) {
  220. Comparer c = new Comparer();
  221. c.printComparison();
  222. }
  223. }

--END--2019年12月22日13:05:12

[SQL]用于提取组内最新数据,左连接,内连接,not exist三种方案中,到底谁最快?的更多相关文章

  1. 一次MySQL两千万数据大表的优化过程,三种解决方案

    问题概述 使用阿里云rds for MySQL数据库(就是MySQL5.6版本),有个用户上网记录表6个月的数据量近2000万,保留最近一年的数据量达到4000万,查询速度极慢,日常卡死.严重影响业务 ...

  2. MySQL冗余数据的三种方案

    一,为什么要冗余数据 互联网数据量很大的业务场景,往往数据库需要进行水平切分来降低单库数据量. 水平切分会有一个patition key,通过patition key的查询能够直接定位到库,但是非pa ...

  3. 针对Hbuilderx内置终端无法输入问题,总结了三种方法供大家参考

    下图,是内置终端无法输入的现象(本人使用的第三种方案,解决了该问题) 第一种解决方案,也是网上推荐最多的方案: 打开Hbuilder安装路径下插件文件夹中的main.js文件:HBuilderX\pl ...

  4. mongo数据同步的三种方案

    (一)直接复制data目录(需要停止源和目标的mongo服务)1.针对目标mongo服务已经存在,并正在运行的(mongo2-->mongo).执行步骤:(1).停止源/目标服务器的mongo服 ...

  5. ios学习网络------4 UIWebView以三种方式中的本地数据

    UIWebView这是IOS内置的浏览器.能够浏览网页,打开文档  html/htm  pdf   docx  txt等待格文档类型. safari浏览器是通过UIWebView制作. server将 ...

  6. 将Excel文件数据导入到SqlServer数据库的三种方案

    方案一: 通过OleDB方式获取Excel文件的数据,然后通过DataSet中转到SQL Server,这种方法的优点是非常的灵活,可以对Excel表中的各个单元格进行用户所需的操作. openFil ...

  7. 通过SQL脚本导入数据到不同数据库避免重复导入三种方式

    前言 无论何种语言,一旦看见代码中有重复性的代码则想到封装来复用,在SQL同样如此,若我们没有界面来维护而且需要经常进行的操作,我们会写脚本避免下次又得重新写一遍,但是这其中就涉及到一个问题,这个问题 ...

  8. SQL后台分页三种方案和分析

    建立表:CREATE TABLE [TestTable] ( [ID] [int] IDENTITY (1, 1) NOT NULL , [FirstName] [nvarchar] (100) CO ...

  9. url地址数据参数转化JSON对象(js三种方法实现)

    当我们用get方法提交表单时,在url上会显示出请求的参数组成的字符串,例如:http://localhost:3000/index.html?phone=12345678901&pwd=12 ...

随机推荐

  1. JavaScript 之 offset 、client、scroll

    下面这三组是关于元素大小.位置相关的属性 一.offset 偏移量 1.offsetParent 该属性获取距离当前元素最近的定位父元素,如果没有定位父元素此时是 body 元素 2.offsetLe ...

  2. 石油petrolaeum单词petrolaeum原油

    petroleum 1.a flammable liquid ranging in color from clear to very dark brown and black, consisting ...

  3. mongodb 通过嵌入文档中的字段排序

    mongodb中的全部数据: db.testInfo.find({}) .sort({_id:-1}) .limit(100) 查询结果: /* 1 createdAt:2019/10/11 下午5: ...

  4. linux防火墙和xshell的链接

    centos7用的firewalld 1.firewalld的基本使用 启动: systemctl start firewalld 关闭: systemctl stop firewalld 查看状态: ...

  5. Golang 在 Mac、Linux、Windows 下如何交叉编译

    转自 https://blog.csdn.net/panshiqu/article/details/53788067 Golang 支持交叉编译,在一个平台上生成另一个平台的可执行程序,最近使用了一下 ...

  6. 通用工业协议(CIP)形式化的安全分析(前期概念的梳理)

    1.CIP的概念的梳理 CIP是为开放的现场总线DeviceNet ControlNet   EtherNet/IP 网络提供公共的应用层和设备描述, CIP是基于对象的协议,使用生产者/消费者模型, ...

  7. 转载_fread函数详解

    fread函数详解 函数原型: size_t   fread(   void   *buffer,   size_t   size,   size_t   count,   FILE   *strea ...

  8. gitlab上下载项目

    第一步:下载安装git,在官网下载安装即可,没有账号的自己注册账号: 第二步:在左面空白处点击鼠标右键,点击Git Bash Here,出现对话框: 第三步:配置本地仓库的账号邮箱git: $ git ...

  9. URI与URL详解

    URL 与 URI 很多人会混淆这两个名词. URL:(Uniform/Universal Resource Locator 的缩写,统一资源定位符). URI:(Uniform Resource I ...

  10. 【大数据】Windows7、Hadoop2.7.6

    一.Java配置 1.完整路径不能有空格:C:\jdk1.8.0_101 2.配置环境变量:JAVA_HOME 二.Hadoop配置 1.完整路径不能有空格:F:\0002_BigData\Soft\ ...