Crecordset::useMultiRowFetch EXTREM LANGSAM



  • Hi,

    lese ich mit der CRecordset Klasse der MFC ca. 1.000.000 Datensätze ein, so dauert dies ca. 5 Sekunden was schon recht gut ist.

    CRecordset recordset(pDatabase);
    			for (recordset.Open(CRecordset::snapshot, sSqlStatement); !recordset.IsEOF(); recordset.MoveNext())
    			{
    			}
    

    Aber es gibt ja noch den "useMultiRowFetch" Modus wo nicht jeder Datensatz einzeln gefetcht wird,
    sondern je nachdem was man mit SetRowsetSize gesetzt hat auch z. B. 1000 Datensätze.
    Meine Erwartung war, dass ich das Einlesen damit nochmal deutlich beschleunigen könnte.
    Vergleichbar wie wenn man anstatt eines INSERT für jeden einzelnen Datensatz ein INSERT mit mehreren Datensätzen macht
    was ja auch ein riesen Unterschied ist.

    Es ist allerdings so dass der useMultiRowFetch Modus EXTREM LANGSAM ist.
    So langsam, dass ich es noch nie habe fertig laufen lassen, weil es wohl ewig dauern würde.
    Logge ich das Einlesen mit kann ich beobachten, dass das Einlesen tatsächlich in 1000er Bulks geschieht "GetRowsFetched".
    Aber liest er zu Beginn noch ca. 12.000 Datensätze pro Sekunde nimmt die Zahl der Datensätze EXPONENTIELL ab!
    D.h. nach ca. 20 Sekunden werden nur noch 1000 Datensätze pro alle 5 Sekunden eingelesen.
    Die CPU Auslastung bleibt konstant bei 25% der RAM Verbrauch bleibt auch konstant.

    WAS IST DA LOS MICROSCHROTT?

    Die Datenbankverbindung ist eine ODBC Verbindung und der Db Server befindet sich auf der selben Maschine (localhost).

                            CRecordset recordset(pDatabase);
    			recordset.SetRowsetSize(1000);
    			for (recordset.Open(CRecordset::forwardOnly, sSqlStatement, CRecordset::readOnly | CRecordset::useMultiRowFetch | CRecordset::useExtendedFetch); !recordset.IsEOF(); recordset.MoveNext())
    			{
    				const Int nRowsFetched = static_cast<Int>(recordset.GetRowsFetched());
    				for (int i = 1; i <= nRowsFetched; ++i)
    				{
    					recordset.SetRowsetCursorPosition(static_cast<WORD>(i));
    
    				}
    			}
    

    Wer kann sich das erklären. MAche ich etwas falsch. Das kann doch nicht sein?



  • Ich habe mit der MFC noch nie etwas gemacht.

    Einen DB Cursor würde ich mit First/Next verwenden und nicht jedes mal die Position setzen. Kein Ahnung, ob es einen Unterschied macht.

    Edit: eigentlich machst du das ja auch in der äußeren Schleife und positionierst ihn dann in der inneren noch einmal von 1 bis Ende, oder verstehe ich das falsch?



  • Ja, man iteriert sozusagen mit CRecordset::MoveNext() nicht über Records, sondern über Rowsets. Ohne "useMultiRowFetch" haben diese Rowsets aber wohl immer nur einen Record. D.h. wenn man SetRowsetSize(1000) macht und nur mit der äußeren Schleife über die Rowsets iteriert, sieht man nur jeden 1000sten Record. Darum braucht es dann die innere Schleife um über die einzelnen Records des Rowset zu iterieren. Der Code ist auch aus einem MSDN Artikel. Lasse ich den "SetRowsetCursorPosition" Aufruf weg dauert das Einlesen dann wieder nur ca. 5 Sekunden. Aber wie gesagt werden dann auch nur 1000 anstatt der 1.000.000 Records gelesen was dann auch wieder ziemlich langsam ist, aber immerhin nicht mehr ewig dauert. Jedenfalls macht der "SetRowsetCursorPosition" Aufruf irgendetwas extrem ineffizientes.

    Hier habe ich sogar eine Implementierung gefunden:

    https://github.com/pixelspark/corespark/blob/master/Libraries/atlmfc/src/mfc/dbcore.cpp

    void CRecordset::SetRowsetCursorPosition(WORD wRow, WORD wLockType)
    {
    	ASSERT(IsOpen());
    	ASSERT(m_dwOptions & useMultiRowFetch);
    
    	RETCODE nRetCode;
    
    	AFX_ODBC_CALL(::SQLSetPos(m_hstmt, wRow, SQL_POSITION, wLockType));
    }
    
    AFX_ODBC_CALL 	Ruft eine ODBC-API-Funktion auf, die zurückgibt SQL_STILL_EXECUTING . AFX_ODBC_CALL Ruft die Funktion wiederholt auf, bis Sie nicht mehr zurückgegeben wird SQL_STILL_EXECUTING .
    

    Vielleicht ist der ODBC Treiber einfach nicht in der Lage MultiRowFetch zu unterstützen oder in der MySQL Datenbank muss noch etwas eingestellt werden. Aber, dass der MultiRowFetch langsamer als der SingleRowFetch ist ist völlig absurd. Falls jemand noch einen Tipp hat?



  • Finde ich aber durchaus wahrscheinlich, dass das an der MySql Implementierung liegt. Da würde ich mal als erstes recherchieren.



  • @Enumerator
    Zwei Dinge die du probieren könntest:

    1. Versuch es mit einer kleineren "set size".
    2. Probier aus wie die Performance ist wenn du MS SQL Server als Backend verwendest (die "Express" Version ist gratis).

    Es kann aber auch leicht sein dass "multi row fetch" mit einem "firehose" Cursor (read only, forward only) einfach nix bringt. Würde mich nicht wundern wenn ODBC Treiber das selbst optimieren und im Hintergrund transparent "multi row fetch" macht. Der SQL Server ADODB Treiber macht es soweit ich weiss.


Log in to reply