DB/2 Performance-Frage

AvS

Erfahrenes Mitglied
Hi,

in welcher Form sollte man INSERT-Anweisungen auf ein DBMS absetzen ? Ich habe es jetzt erstmal zum Testen in folgender Form gemacht :

Java:
public static void fill_branches(Connection conn, int n, int i) {
	double start = System.currentTimeMillis();
	try {
	    while (i <= n) {
		Random rand = new Random();
		int x = rand.nextInt(n) + 1;
		PreparedStatement stmt = conn
			.prepareStatement("INSERT INTO BRANCHES (BRANCHID, BRANCHNAME, BALANCE, ADDRESS)"
				+ "VALUES ("
				+ i
				+ ", 'string' , "
				+ x
				+ " , 'string')");
		stmt.executeUpdate();
		i++;
	    }
	    conn.commit();
	    double stop = System.currentTimeMillis();
	    double ergebnis = (start - stop) / 1000;
	    System.out.println("Neue Daten erfolgreich " + ergebnis
		    + " Sekunden in BRANCHES eingetragen !");
	} catch (SQLException e) {
	    e.getMessage();
	}
    }

Nur wird dort ja bei jedem Schleifendurchlauf ein neues Statement aufgerufen, was ich mit bei vielen Einträgen ( n > 100000 ) schon als problematisch vorstellen könnte.
Gibt es also Möglichkeiten, die das irgendwie performanter machen ?
 
Moin AvS,

es läßt sich ein bisschen schwer erkennen, was denn in der Echt-Applikation der Inhalt der vielen neuen Sätze Deiner proof-of-concept-Skizze sein werden, also was der fachliche Hintergrund ist.

Wenn die Mimik wirklich so simpel ist wie oben, dann würde ic (auch in dieser Reihenfolge) diese Änderungen prüfen:
- wenn machbar, lass eine Stored Procdure diesen Massen-Insert machen (mit dem Parameter n für n Sätze sind anzulegen). Macht ja keinen Sinn, da 100000 Statements vom Client aus abzufeuern.
- wenn die BranchId wirklich einfach nur hochgezählt wird, dann mach dieses Feld auch zum IDENTITY-Feld und überlass die ID-Vergabe der Datenbankengine. Raus damit aus dem INSERT-Statement

Randbemerkung: Den Commit-nach-x-Sätzen solltest Du selbst übernehmen, also alle 5000 oder 10000 Sätze einen COMMIT machen und nicht pauschal einen nach allen Sätzen.

Grüße
Biber
 
Zuletzt bearbeitet:
Hi,

also es werden mit dieser Funktion schon Daten eingetragen, allerdings auch nur bis ca. n = 1000. Dann macht er nicht mehr mit ! Da ich die Datenbank ja auf meinem Laptop installiert habe, spielen da nicht Sachen wie Arbeitsspeicher und Festplattenspeicher eine begrenzende Rolle ?

Edit : Habe die Funktion für eine andere Tabelle umgeschrieben. Eingetragen wird jetzt allerdings nicht !

Java:
    public static void fill_accounts_diff(Connection conn, int n, int i) throws SQLException
    {
	double start = System.currentTimeMillis();
	PreparedStatement stmt = null;
	try
	{
	    int count = n * 10;
	    String sql = "INSERT INTO accounts values(?, ?, ?, ?, ?)";
	    stmt = conn.prepareStatement(sql);
	    while( i <= count)
	    {
		Random rand = new Random();
		int x = rand.nextInt(n) + 1;
		stmt.setInt(1, i); 
		stmt.addBatch();
		stmt.setString(2, "aaaaaaaaaaaaaaaaaa"); 
		stmt.addBatch();
		stmt.setInt(3, 0); 
		stmt.addBatch();
		stmt.setInt(4, x); 
		stmt.addBatch();
		stmt.setString(5, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); 
		stmt.addBatch();
		stmt.executeBatch();
	    }
	    stmt.close();
	    conn.commit();
}catch(SQLException e)
	{
	    e.getSQLState();
	}
    }
 
Zuletzt bearbeitet:
Moin AvS,

ich will da Darimont nicht vorgreifen, weil das ohnehin nicht der Weg wäre, den ich eingeschlagen hätte, aber...
IMHO verwendest Du jetzt die PreparedStatement:.addBatch()-Methode falsch.
Du musst schon zu jedem einzelnen Statement die vollständige Parameterliste mit setString()/setLong() befüllen (oder zumindest alle Parameter, die einen not-default-Wert haben sollen).

Also bitte NICHT so wie oben:
Code:
....
// hier füllst Du NUR Param1 des PreparedStatementNo#1
setString( parameterIndex1, 'bla');
 addBatch();
// hier füllst Du NUR Param2 des PreparedStatementNo#2
setLong( parameterIndex2, 4711); 
addBatch();

// hier füllst Du NUR Param3 des PreparedStatementNo#3
setString( parameterIndex3, 'bla'); 
addBatch();
....
// und abfeuern

-->führt dazu, das jedes der einzelnen PrepStatement einen Insert versuchen soll, der ein INSERT mit 3 NULL-Werten und einem "richtigen" Wert machen soll.

Aber so weit kommt es vermutlich gar nicht - jedenfalls ist ja kein SQLError bei Dir hochgepoppt.->also ist es ein Nicht-SQLFehler.

Anyhow, lies bitte noch einmal die beiden Links, die Darimont gepostet hat und korrigiere das Statement dahingehend, dass Du erst alle 5 Parameter setzt (wenn das INSERT 5 Werte braucht) und dann dieses "gefüllte" bzw. parametrisierte Statement cacht mit addBatch().
Fülle so in einer dieser putzigen Schleifen ja 20 Inserts und feuere dann ein Execute ab.
Alle 5000 Inserts oder umgerechnet alle 250 Schleifen ein Commit.

Und dann sag an, ob die Performance sich spürbar verbessert hat oder ob wir mal was anderes versuchen.

Grüße
Biber
 
Zuletzt bearbeitet:
Hä ? Die geänderte Funktion macht doch genau das, was du gerade forderst oder nicht ?

Dein Code :
....
// hier füllst Du NUR Param1 des PreparedStatementNo#1
setString( parameterIndex1, 'bla');
addBatch();


Mein Code :
stmt.setInt(1, i);
stmt.addBatch();


Und in der Form steht es doch auch in der API
 
Moin AvS,

sorry, wenn mein letzter Kommentar so zu Missverständnissen geführt hat.
Frei zitiert hatte in der Tat DEINE fill_accounts_diff().
Alledings mit Kommentaren an den Stellen, an denen ich der Meinung bist, dass Du die Funktion falsch verstanden hast.
Ich versuche es mal anders. Richtiger wäre IMHO
[[ das ist DEINE Funktion an den Stellen kommentiert , wo ich ändern würde!!]
Java:
    public static void fill_accounts_diff(Connection conn, int n, int i) throws SQLException
    {
	double start = System.currentTimeMillis();
	PreparedStatement stmt = null;
	try
	{
	    int count = n * 10;
	    String sql = "INSERT INTO accounts values(?, ?, ?, ?, ?)";
	    stmt = conn.prepareStatement(sql);
	    while( i <= count)
	    {
		Random rand = new Random();
		int x = rand.nextInt(n) + 1;
		stmt.setInt(1, i); 
		// der soll raus: stmt.addBatch();
		stmt.setString(2, "aaaaaaaaaaaaaaaaaa"); 
		// der soll raus: stmt.addBatch();
		stmt.setInt(3, 0); 
		// der soll raus: stmt.addBatch();
		stmt.setInt(4, x); 
		// der soll raus: stmt.addBatch();
		stmt.setString(5, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); 
              // .... Jetzt sind ALLE Parameter EINES Statements 
              // .........Jetzt ist auch ein .addBatch() dran
		stmt.addBatch();
// .............. aber: jetzt ist noch KEIN executeBatch() dran, sondern erst,
//............. wenn Du 20 oder 50 INSERTs nach diesem Muster vorbereitet hast.

// DIESE ZEILE SOLL ERST nach 20 vorbereiteten Inserts kommen
/// ---------<		stmt.executeBatch();
	    }
	    stmt.close();
	    conn.commit();
}catch(SQLException e)
	{
	    e.getSQLState();
	}
    }

--> Du brauchst also wie gestern versucht herzuleiten, ZWEI Schleifen/Loop
- die innere [" prepareInsertsAsBatch()" ] bereitet jeweils 20 oder 50 Inserts vor, danach wird das als EIN Statement executed
- die äußere Schleife portioniert die Gesamt-Inserts, also ruft die innere Funktion so lange auf, bis alle 100000 Datensätze häppchenweise per executeBatch() gegen den Server gesendet worden sind.

Aber ich glaube, ich lass mal jemand anders erklären; das ist nicht meine Stärke.

Grüße
Biber
 
Zuletzt bearbeitet:
Hi,

habe selber noch ein wenig probiert und getestet und jetzt werden die Daten korrekt eingetragen. Habe zusätzlich auch noch den Speicherplatz für die Logfile der DB2-Datenbank aufs Maximum hochgeschraubt.

Java:
public static void fill_branches(Connection conn, double n, int i) {
		double start = System.currentTimeMillis();
		try {
		   	String sql = "INSERT INTO BRANCHES (BRANCHID, BRANCHNAME, BALANCE, ADDRESS) VALUES (?, ?, ?, ?)";
			PreparedStatement stmt = conn.prepareStatement(sql);
			for(i=1; i<=n; i++){			
			stmt.addBatch(sql);
			stmt.setInt(1, i);
			stmt.setString(2, "aaaaaaaaaaaaaaaaaaaa");
			stmt.setInt(3, 0);
			stmt.setString(4, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
			stmt.executeUpdate();
			}
			conn.commit();
		    double stop = System.currentTimeMillis();
		    double ergebnis = (start - stop) / 1000;
		    System.out.println("Neue Daten erfolgreich " + ergebnis
			    + " Sekunden in BRANCHES eingetragen !");
	    }catch(SQLException e)
	    {
	    	e.getMessage();
	    }
	}
 
Hallo,

ich muss nochmal nerven ;)
Ich habe die Inserts nun folgendermaßen implementiert. Die laufen auch alle durch, nur für n = 2.500.000 brauche ich mit meinem Laptop rund 4 Minuten. Liegt es primär an der Hardware oder doch am Skript ?

Java:
PreparedStatement stmt_te = conn.prepareStatement("INSERT INTO TELLERS VALUES (?, ?, ?, ?, ?)");
		while(i<=count)
		{			
		Random rand = new Random();
		int x = rand.nextInt(n) + 1;
		stmt_te.setInt(1, i);
		stmt_te.setString(2, "aaaaaaaaaaaaaaaaaaaa");
		stmt_te.setInt(3, 0);
		stmt_te.setInt(4, x);
		stmt_te.setString(5, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
		stmt_te.addBatch();
		i++;
		}
		stmt_te.executeBatch();
		stmt_te.close();
		conn.commit();
 
Hallo,

es reicht wenn du außerhalb der Schleife ein Random instanziierst.
Weiterhin solltest du alle 2500 / 5000 Sätze einmal executeBatch() machen.

Gruß Tom
 
Zurück