数据库排序与编程 java 排序

我希望通过 JPA 从数据库(MySQL)中获得数据,我希望它按一些列值排序。

那么,最好的做法是:

  • 以对象列表(JPA)的形式从数据库中检索数据,然后 使用一些 java API 以编程方式对它进行排序。

或者

  • 让数据库使用排序选择查询对其进行排序。

先谢谢你

38387 次浏览

我几乎可以肯定,让数据库对它进行排序会更快。有些工程师花费大量时间来完善和优化他们的搜索算法,而你必须实现自己的排序算法,这可能会增加一些计算。

我会让数据库做排序,他们一般非常擅长这一点。

一般来说,最好在 SQL 查询中使用 ORDER BY——这样,如果有一个适用的索引,您可能会得到“免费”的排序(最糟糕的情况是,它的工作量与在代码中进行排序的工作量相同,但通常可能比这个工作量要少!).

如果要检索所有数据库数据的一个子集,例如在屏幕上显示1000行中的20行,最好在数据库上进行排序。这将更快、更容易,并且允许您一次检索一页行(20、50、100) ,而不是所有行。

如果数据集相当小,则如果希望实现复杂排序,则在代码中进行排序可能更方便。通常这种复杂的排序可以在 SQL中完成,但不像在代码中那样容易。

简而言之,经验法则是通过 SQL对规则进行排序,其中包含一些边缘情况。

这并不完全正确,但我最近发表了一些与数据库和应用程序端排序相关的文章。这篇文章是关于。网络技术,所以大多数可能不会让你感兴趣,但基本原则仍然是:

将排序推迟到客户端(例如 jQuery、 Dataset/Dataview 排序)可能看起来很诱人。而且它实际上是一个可行的分页、排序和过滤选项,如果(并且仅仅如果) :

1. 数据集很小,并且

1. 对性能和可伸缩性几乎没有关注

根据我的经验,符合这种标准的系统很少。请注意,在应用程序/数据库中不可能混合和匹配排序/分页ーー如果您要求数据库提供未排序的100行数据,那么在应用程序端对这些行进行排序,您可能无法获得预期的数据集。这可能看起来很明显,但是我已经看到这个错误犯了很多次,我想至少提一下。

由于以下原因,在数据库中进行排序和过滤要有效得多。一方面,数据库引擎高度优化,以完成排序和筛选所需的工作; 这就是它们的底层代码设计的目的。但是,即使不考虑这一点(即使假设您可以编写与成熟数据库引擎的排序、过滤和分页性能相匹配的代码) ,在数据库中进行这项工作仍然是可取的,原因很简单,限制从数据库传输到应用服务器的数据量更有效。

例如,如果在过滤之前有10,000行,而你的查询将这个数字减少到75行,那么在客户端进行过滤将导致所有10,000行的数据通过网络传递(并进入应用服务器的内存) ,在数据库端进行过滤将导致只有经过过滤的75行在数据库和应用程序之间移动。它可以对性能和可伸缩性产生巨大的影响。

全文如下: Http://psandler.wordpress.com/2009/11/20/dynamic-search-objects-part-5sorting/

让数据库对它进行排序,这样就可以很容易地使用 JPA 进行分页,而无需读取整个结果集。

我遇到了同样的问题,并决定我应该运行一个小基准,以量化速度差异。结果出乎我的意料。我想把我对这类问题的经验贴出来。

和这里的其他一些海报一样,我的想法是数据库层可以更快地进行排序,因为它们应该针对这种情况进行了调优。@ Alex 提出了一个很好的观点,如果数据库已经有了排序的索引,那么它会更快。我想回答这样一个问题: 对于非索引排序,哪种原始排序更快。注意,我说的是更快,不是更简单。我认为在许多情况下,让数据库完成这项工作更简单,也更不容易出错。

我的主要假设是,这种类型可以放入主存中。不是所有的问题都适合这里,但有很多问题适合。对于内存不足的排序,数据库很可能在这里发挥作用,尽管我没有测试这一点。在内存排序的情况下,所有的 java/c/c + + 在我的非正式基准测试中都优于 mysql,如果可以这样称呼它的话。

我希望我有更多的时间来更彻底地比较数据库层和应用程序层,但遗憾的是其他职责要求。尽管如此,我还是忍不住为其他沿着这条路走下去的人录下了这个音符。

当我沿着这条路走下去的时候,我开始看到更多的障碍。我应该比较数据传输吗?怎么做到的?我可以比较读取数据库的时间和读取 Java 平面文件的时间吗?如何隔离排序时间和数据传输时间以及读取记录的时间?这些问题包括我提出的方法和计时数字。

所有时间以毫秒表示,除非另有说明

所有的排序例程都是语言提供的默认值(这些对于随机排序的数据来说已经足够好了)

所有编译都是通过 netbeans 选择的典型“发布配置文件”,除非另有发布,否则不进行自定义

Mysql 的所有测试都使用以下模式

 mysql> CREATE TABLE test_1000000
(
pk bigint(11) NOT NULL,
float_value DOUBLE NULL,
bigint_value     bigint(11)  NULL,
PRIMARY KEY (pk )
) Engine MyISAM;


mysql> describe test_1000000;
+--------------+------------+------+-----+---------+-------+
| Field        | Type       | Null | Key | Default | Extra |
+--------------+------------+------+-----+---------+-------+
| pk           | bigint(11) | NO   | PRI | NULL    |       |
| float_value  | double     | YES  |     | NULL    |       |
| bigint_value | bigint(11) | YES  |     | NULL    |       |
+--------------+------------+------+-----+---------+-------+

首先,这里是一个用于填充 DB 的小代码片段。也许有更简单的方法,但这就是我所做的:

public static void BuildTable(Connection conn, String tableName, long iterations) {
Random ran = new Random();
Math.random();
try {




long epoch = System.currentTimeMillis();
for (long i = 0; i < iterations; i++) {
if (i % 100000 == 0) {
System.out.println(i + " next 100k");
}
PerformQuery(conn, tableName, i, ran.nextDouble(), ran.nextLong());
}


} catch (Exception e) {
logger.error("Caught General Exception Error from main " + e);


}
}

MYSQL Direct CLI 结果:

select * from test_10000000 order by bigint_value limit 10;
10 rows in set (2.32 sec)

这些计时有些困难,因为我所知道的唯一信息是执行命令后报告的时间。

在 mysql 提示符中,对于10000000个元素,对于 bigint _ value 或 float _ value 的排序,大约是2.1到2.4

JavaJDBC mysql 调用(类似于从 mysql cli 进行排序的性能)

public static void SortDatabaseViaMysql(Connection conn, String tableName) {


try {
Statement stmt = conn.createStatement();
String cmd = "SELECT * FROM " + tableName + " order by float_value limit 100";




ResultSet rs = stmt.executeQuery(cmd);
} catch (Exception e) {


}


}

五次:

da=2379 ms
da=2361 ms
da=2443 ms
da=2453 ms
da=2362 ms

动态生成随机数(实际上比磁盘 IO 读取慢)。赋值时间是生成随机数并填充数组的时间

打电话

JavaSort(10,10000000);

计时结果:

assignment time 331  sort time 1139
assignment time 324  sort time 1037
assignment time 317  sort time 1028
assignment time 319  sort time 1026
assignment time 317  sort time 1018
assignment time 325  sort time 1025
assignment time 317  sort time 1024
assignment time 318  sort time 1054
assignment time 317  sort time 1024
assignment time 317  sort time 1017

这些结果用于在二进制模式下读取双精度文件

assignment time 4661  sort time 1056
assignment time 4631  sort time 1024
assignment time 4733  sort time 1004
assignment time 4725  sort time 980
assignment time 4635  sort time 980
assignment time 4725  sort time 980
assignment time 4667  sort time 978
assignment time 4668  sort time 980
assignment time 4757  sort time 982
assignment time 4765  sort time 987

执行缓冲区传输会导致更快的运行时

assignment time 77  sort time 1192
assignment time 59  sort time 1125
assignment time 55  sort time 999
assignment time 55  sort time 1000
assignment time 56  sort time 999
assignment time 54  sort time 1010
assignment time 55  sort time 999
assignment time 56  sort time 1000
assignment time 55  sort time 1002
assignment time 56  sort time 1002

C 和 C + + 计时结果(参见下面的源代码)

使用 qsort 调试配置文件

assignment 0 seconds 110 milliseconds   Time taken 2 seconds 340 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 2 seconds 340 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 2 seconds 330 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 2 seconds 340 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 2 seconds 330 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 2 seconds 340 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 2 seconds 340 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 2 seconds 330 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 2 seconds 340 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 2 seconds 330 milliseconds

使用 qsort 发布配置文件

assignment 0 seconds 100 milliseconds   Time taken 1 seconds 600 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 600 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 580 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 590 milliseconds
assignment 0 seconds 80 milliseconds    Time taken 1 seconds 590 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 590 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 600 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 590 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 600 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 580 milliseconds

使用 std: : sort (a,a + ARRAY _ SIZE)发布配置文件;

assignment 0 seconds 100 milliseconds   Time taken 0 seconds 880 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 0 seconds 870 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 0 seconds 890 milliseconds
assignment 0 seconds 120 milliseconds   Time taken 0 seconds 890 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 0 seconds 890 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 0 seconds 880 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 0 seconds 900 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 0 seconds 890 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 0 seconds 890 milliseconds
assignment 0 seconds 150 milliseconds   Time taken 0 seconds 870 milliseconds

发布配置文件从文件中读取随机数据并使用 std: : sort (a,a + ARRAY _ SIZE)

assignment 0 seconds 50 milliseconds    Time taken 0 seconds 880 milliseconds
assignment 0 seconds 40 milliseconds    Time taken 0 seconds 880 milliseconds
assignment 0 seconds 50 milliseconds    Time taken 0 seconds 880 milliseconds
assignment 0 seconds 50 milliseconds    Time taken 0 seconds 880 milliseconds
assignment 0 seconds 40 milliseconds    Time taken 0 seconds 880 milliseconds

下面是所使用的源代码

Java 源代码 请注意,JavaSort 的内部 runCode 和 writeFlag 需要根据您想要计时的内容进行调整。还要注意,内存分配发生在 for 循环中(因此测试了 GC,但是我没有看到任何明显的差异将内存分配移出循环)

public static void JavaSort(int iterations, int numberElements) {


Random ran = new Random();
Math.random();
int runCode = 2;
boolean writeFlag = false;
for (int j = 0; j < iterations; j++) {
double[] a1 = new double[numberElements];
long timea = System.currentTimeMillis();
if (runCode == 0) {
for (int i = 0; i < numberElements; i++) {
a1[i] = ran.nextDouble();


}
}
else if (runCode == 1) {


//do disk io!!
try {
DataInputStream in = new DataInputStream(new FileInputStream("MyBinaryFile.txt"));
int i = 0;
//while (in.available() > 0) {
while (i < numberElements) { //this should be changed so that I always read in the size of array elements
a1[i++] = in.readDouble();
}
}
catch (Exception e) {


}


}
else if (runCode == 2) {
try  {
FileInputStream stream = new FileInputStream("MyBinaryFile.txt");
FileChannel inChannel = stream.getChannel();


ByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
//int[] result = new int[500000];


buffer.order(ByteOrder.BIG_ENDIAN);
DoubleBuffer doubleBuffer = buffer.asDoubleBuffer();
doubleBuffer.get(a1);
}
catch (Exception e) {


}
}


if (writeFlag) {
try {
DataOutputStream out = new DataOutputStream(new FileOutputStream("MyBinaryFile.txt"));
for (int i = 0; i < numberElements; i++) {
out.writeDouble(a1[i]);
}
} catch (Exception e) {


}
}
long timeb = System.currentTimeMillis();
Arrays.sort(a1);


long timec = System.currentTimeMillis();
System.out.println("assignment time " + (timeb - timea) + " " + " sort time " + (timec - timeb));
//delete a1;
}
}

C/C + + 源代码

#include <iostream>
#include <vector>
#include <algorithm>
#include <fstream>


#include <cstdlib>
#include <ctime>
#include <cstdio>
#include <math.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>


#define ARRAY_SIZE 10000000


using namespace std;


int compa(const void * elem1, const void * elem2) {
double f = *((double*) elem1);
double s = *((double*) elem2);
if (f > s) return 1;
if (f < s) return -1;
return 0;
}


int compb (const void *a, const void *b) {
if (*(double **)a < *(double **)b) return -1;
if (*(double **)a > *(double **)b) return 1;
return 0;
}


void timing_testa(int iterations) {


clock_t start = clock(), diffa, diffb;


int msec;
bool writeFlag = false;
int runCode = 1;


for (int loopCounter = 0; loopCounter < iterations; loopCounter++) {
double *a = (double *) malloc(sizeof (double)*ARRAY_SIZE);
start = clock();
size_t bytes = sizeof (double)*ARRAY_SIZE;
if (runCode == 0) {
for (int i = 0; i < ARRAY_SIZE; i++) {
a[i] = rand() / (RAND_MAX + 1.0);
}
}
else if (runCode == 1) {
ifstream inlezen;


inlezen.open("test", ios::in | ios::binary);




inlezen.read(reinterpret_cast<char*> (&a[0]), bytes);


}
if (writeFlag) {
ofstream outf;
const char* pointer = reinterpret_cast<const char*>(&a[0]);
outf.open("test", ios::out | ios::binary);
outf.write(pointer, bytes);
outf.close();


}


diffa = clock() - start;
msec = diffa * 1000 / CLOCKS_PER_SEC;
printf("assignment %d seconds %d milliseconds\t", msec / 1000, msec % 1000);
start = clock();
//qsort(a, ARRAY_SIZE, sizeof (double), compa);
std::sort( a, a + ARRAY_SIZE );
//printf("%f %f %f\n",a[0],a[1000],a[ARRAY_SIZE-1]);
diffb = clock() - start;


msec = diffb * 1000 / CLOCKS_PER_SEC;
printf("Time taken %d seconds %d milliseconds\n", msec / 1000, msec % 1000);
free(a);
}






}


/*
*
*/
int main(int argc, char** argv) {


printf("hello world\n");
double *a = (double *) malloc(sizeof (double)*ARRAY_SIZE);




//srand(1);//change seed to fix it
srand(time(NULL));


timing_testa(5);






free(a);
return 0;
}

这个问题没有一个直接的答案,必须根据上下文来回答。

您的应用程序(中间层)是否与数据库运行在同一个节点上?

如果是,则不必担心数据库和中间层之间的延迟。然后问题变成了: 查询的子集/结果集有多大?请记住,要对这个中间层进行排序,您将获取一个大小为 N 的列表/集合,并编写一个自定义比较器或使用默认的 Collection 比较器。随便啦。所以一开始,你就被 N 的大小挫败了。

但是如果答案是否定的,那么将结果集从 DB 传输到中间层所涉及的延迟就会对您造成影响。然后,如果您执行分页,这是最后一件应该做的事情,那么在切割页面之后,您将丢弃90-95% 的结果集。

所以浪费的带宽是不合理的。想象一下,在您的租户组织中,对每个请求都这样做。

不管你怎么看,这都是糟糕的设计。

无论如何,我都会在数据库里做这个。仅仅因为现在几乎所有的应用程序都需要分页; 即使它们不通过网络向您的客户发送大量的结果集,也是一种彻底的浪费; 将所有的租户都拖下水。

这些天我一直在玩弄的一个有趣的想法是利用 HTML5的力量,在诸如 Angular 这样的浏览器框架中实现双向数据绑定,并将一些处理推回到浏览器。这样的话,你就不用排队等别人来完成了。真正的分布式处理。但在决定哪些可以推动,哪些不可以推动时,必须谨慎。

取决于具体情况。

DR

如果应用程序服务器中有完整的数据,请在应用程序服务器中执行此操作。

如果在应用服务器端已经有了所需的完整数据集,那么最好在应用服务器端执行此操作,因为这些服务器可以水平伸缩。最有可能出现的情况是:

  • 从数据库中检索的数据集很小
  • 您在启动时将数据缓存在应用程序服务器端
  • 您正在进行事件源,并且无论如何都要在应用程序服务器端构建数据。

不要在客户端这样做,除非你能保证它不会影响客户端设备。

数据库本身可以进行优化,但是如果能够减轻数据库的负担,就可以降低总体成本,因为扩展数据库比扩展应用服务器更加昂贵。