1

我目前正在研究用 java(7) 实现的桌面应用程序,除其他外,它管理 sql 数据库中的大量数据记录。只有几张表,但它们包含大量记录。我需要对单个表执行复杂的查询,但不需要复杂的连接操作。

到目前为止,我一直在使用 postgres。但由于它是一个桌面单用户应用程序,我也考虑使用 sqlite(不用说,这也会降低设置的复杂性)。所以我写了一个简单的python脚本来做一些性能测试。令我惊讶的是,首先,sqlite 的实际执行情况如何,其次,在 python 中,查询响应时间比在 java 中小得多。

一个常见的场景是根据 id 列表选择一批记录。在python中,我使用以下代码来测试响应时间:

rand_selection = ','.join([str(int(random.random()* MAX_INDEX )) for i in xrange(PAGE_SIZE)])
start = time.time();
c = db.cursor();
res = c.execute("SELECT * FROM bigtable WHERE id in ("+rand_selection+")");
reslist = [str(t) for t in res]; c.close();
print( time.time() - start );

对于 MAX_INDEX=111000 和 PAGE_SIZE=100,这给了我大约 5 毫秒的增量。

嗯,太好了。现在,让我们转到 java:我使用jdbc-sqlite驱动程序。我在完全相同的表上执行了完全相同的查询,查询时间始终在 200 毫秒左右,这对于我的用例来说是不可接受的。

我错过了什么吗?

我知道这是一个非常普遍的问题。但也许有人对 jdbc-sqlite 有一些经验,并且从经验中知道发生了什么……</p>

[编辑]:按照建议使用 timit.default_timer() (感谢 Martijn Pieters)给了我类似的结果。

[Edit2]:按照 CL 的建议,我写了一个简化版的 java 代码。使用此代码我无法验证结果,响应时间与 python 代码大致相同。但是,我在另一台机器上使用不同的 jdk(openjdk7 与 oracle jdk7)进行了测试。诚然,我的其他测试代码很可能存在一些问题。

[编辑 2013-08-16]:我现在使用原始设置执行了相同的测试。我还将它与 postgres 进行了比较。

Model Name:    MacBook Pro
Model Identifier: MacBookPro5,5
Processor Name:   Intel Core 2 Duo
Processor Speed:  2.53 GHz
Memory: 8GB
OS-Version: 10.8.4
Java:
Java(TM) SE Runtime Environment (build 1.7.0_21-b12)
Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode)

测试代码(请原谅草率的编码......):

package ch.dsd;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class Main {

    private static int COL_COUNT = 20;
    private static int TESTRUNS = 20;
    private static int INDEX_COUNT = 64;
    /*
    CREATE TABLE bigtable ( id INTEGER PRIMARY KEY ASC, prop0 real, prop1 real, ... , prop19 real );
     */
    static class Entity {
        private long id;
        private ArrayList<Double> properties = new ArrayList<Double>(COL_COUNT);

        public Entity() {
            for( int i = 0; i < COL_COUNT; i++) {
                properties.add(0.0);
            }
        }

        public long getId() {
            return id;
        }

        public void setId(long id) {
            this.id = id;
        }

        public void setProperty(int idx, double prop) {
            properties.set(idx, prop);
        }

        public double getProperty(int idx) {
            return properties.get(idx);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            for( double prop: properties ) {
                sb.append(prop);
                sb.append(",");
            }
            sb.delete(sb.length()-1, sb.length());
            return sb.toString();
        }
    }

    private static String placeholders( int n ) {
        StringBuilder sb = new StringBuilder();
        if( n > 0 ) {
            sb.append("?");
            for( int i = 1; i < n; i++ )
                sb.append(",?");
            return sb.toString();
        }
        return "";
    }

    private static void setRandomIdcs( PreparedStatement ps, int start, int stop, int max ) throws SQLException {
        for( int i = start; i <= stop; i++ ) {
            ps.setLong(i, (long) ((double) max * Math.random()));
        }
    }

    private static void setRandomValues( PreparedStatement ps, int start, int stop ) throws SQLException {
        for( int i = start; i <= stop; i++ ) {
            ps.setDouble(i, Math.random());
        }
    }


    private static void readFromResultSet( ResultSet rs, List<Entity> lst ) throws SQLException {
        while(rs.next()) {
            final Entity e = new Entity();
            e.setId(rs.getLong(1));
            for( int i = 0; i < COL_COUNT; i++ )
                e.setProperty(i, rs.getDouble(i+2));
            lst.add(e);
        }
    }

    public static void performTest(Connection c) throws SQLException {
        final PreparedStatement ps = c.prepareStatement("SELECT * FROM bigtable WHERE id in ("+placeholders(INDEX_COUNT)+")");
        ArrayList<Entity> entities = new ArrayList<Entity>();
        for( int i = 0; i < TESTRUNS; i++ ) {
            setRandomIdcs( ps, 1, INDEX_COUNT, 1000000 ); // there are one million entries stored in the test table
            long start = System.currentTimeMillis();
            final ResultSet rs = ps.executeQuery();
            readFromResultSet(rs, entities);
            // System.out.println(entities.get(INDEX_COUNT-1));
            System.out.println("Time used:" + (System.currentTimeMillis() - start));
            System.out.println("Items read:" + entities.size());
            rs.close();
            entities.clear();
        }
        ps.close();
    }

    public static void createPSQLTable(Connection c) throws SQLException {
        final String create_stmt = "CREATE TABLE IF NOT EXISTS bigtable (id SERIAL PRIMARY KEY, " +
                "prop0 double precision,prop1 double precision,prop2 double precision,prop3 double precision,prop4 double precision,prop5 double precision,prop6 double precision,prop7 double precision,prop8 double precision,prop9 double precision,prop10 double precision,prop11 double precision,prop12 double precision,prop13 double precision,prop14 double precision,prop15 double precision,prop16 double precision,prop17 double precision,prop18 double precision,prop19 double precision)";
        final PreparedStatement ps = c.prepareStatement(create_stmt);
        ps.executeUpdate();
        ps.close();
    }

    public static void loadPSQLTable( Connection c ) throws SQLException {
        final String insert_stmt = "INSERT INTO bigtable VALUES (default, " + placeholders(20) + ")";
        final PreparedStatement ps = c.prepareStatement(insert_stmt);
        for( int i = 0; i < 1000000; i++ ) {
            setRandomValues(ps, 1, 20);
            ps.executeUpdate();
        }
        c.commit();
    }

    public static void main(String[] args) {
        Connection c = null;
        try {
            Class.forName("org.sqlite.JDBC");
            c = DriverManager.getConnection("jdbc:sqlite:/Users/dsd/tmp/sqlitetest/testdb.db");
            c.setAutoCommit(false);
            performTest(c);
            c.close();
            System.out.println("POSTGRES");
            System.out.println("========");
            final Properties props = new Properties();
            props.setProperty("user", "dsd");
            c = DriverManager.getConnection("jdbc:postgresql:testdb", props);
            c.setAutoCommit(false);
            createPSQLTable(c);
            // loadPSQLTable(c);
            performTest(c);
            c.close();
        } catch ( Exception e ) {
            System.err.println( e.getClass().getName() + ": " + e.getMessage() );
            System.exit(0);
        }
    }
}

结果:

Time used:348
Items read:64
Time used:407
Items read:64
Time used:259
Items read:64
Time used:341
Items read:64
Time used:325
Items read:64
Time used:145
Items read:64
Time used:70
Items read:64
Time used:98
Items read:64
Time used:91
Items read:64
Time used:134
Items read:64
Time used:68
Items read:64
Time used:51
Items read:64
Time used:51
Items read:64
Time used:51
Items read:64
Time used:55
Items read:64
Time used:67
Items read:64
Time used:56
Items read:64
Time used:90
Items read:64
Time used:56
Items read:64
Time used:51
Items read:64
POSTGRES
========
Time used:75
Items read:64
Time used:58
Items read:64
Time used:31
Items read:64
Time used:26
Items read:64
Time used:34
Items read:64
Time used:6
Items read:64
Time used:5
Items read:64
Time used:4
Items read:64
Time used:5
Items read:64
Time used:6
Items read:64
Time used:5
Items read:64
Time used:6
Items read:64
Time used:4
Items read:64
Time used:28
Items read:64
Time used:3
Items read:64
Time used:4
Items read:64
Time used:4
Items read:64
Time used:4
Items read:64
Time used:3
Items read:64
Time used:5
Items read:64
4

1 回答 1

1

Python 是用 C 语言编写的,并且有“sqlite”可执行文件,也是用 C 语言编写的,链接在其中。

没有数据编组或格式之间的转换,因为 Python 和底层 sqlite 库都使用相同的数据类型和编码,这些数据类型和编码对于它们编译的任何平台都是原生的。

另一方面,Java(JVM 也是用 C 编写的,但是 ....)使用特定的平台独立数据类型,特别是所有字符串都是 unicode。为了与底层的 sqlite 可执行文件进行通信,java 库必须使用 JNI,这(通常)涉及数据类型和字符编码的一些转换。在将 C 字符串转换为 unicode 并再次转换回来时,这可能会占用大量 CPU。

说了这么多,我经常使用 sqliteJDBC jar,但从未真正注意到任何性能问题。

您可以尝试将 JavaDB(又名 Derby)视为嵌入式 Java 数据库。它是用纯 Java 编写的,使用“本机”Java 编码并且是“零维护”。

于 2013-08-15T10:10:32.257 回答