Results 1 to 3 of 3

Thread: GRRROOOOTTTE TADOQuery blobs

  1. #1
    Fornicatorus Formicidae VideoRipper's Avatar
    Join Date
    Mar 2005
    Location
    Vicus Saltus Orientalem
    Posts
    5,708

    GRRROOOOTTTE TADOQuery blobs

    Hee allemaal,

    Ik ben bestanden als blobjes in SQL server aan het zetten en dat werkt allemaal prima.
    Echter, wanneer een bestand groter is dan, pak 'm beet, 400MB dan krijg ik een "Out of memory" fout.

    Het maakt daarbij ook niet uit of ik de blob via een parameter inlaadt, of dmv een insert en een TBlobStream.
    Soms krijg ik voor de "Out of memory"-melding nog een foutmelding over het foutgaan van het creëeren van
    een "Safe array" (iets met variants), dus het gebeurt echt in de Delphi-code bij het voorbereiden van de query
    nog voordat het over TADOConnection naar de server wordt gestuurd.

    Nu lees ik op verschillende plekken op internet dat ~400MB een bekende Delphi memory manager limiet is en
    op zich is de kans klein dat er (regelmatig) echt grote blobs opgeslagen moeten worden, maar het stoort mij
    enorm dat ik zoiets (ogenschijnlijk) eenvoudigs niet voor elkaar krijg in Delphi, terwijl SQL server zelf het wel
    gewoon aankan.

    Nu ben ik ook dit stukje proza van Microsoft tegengekomen, dat weer wat hoop geeft (ik moet alleen nog even
    kijken of ik dat in Delphi ook voor elkaar kan krijgen), maar wellicht dat anderen hier al eerder met dit bijltje
    gehakt hebben en mij op weg kunnen helpen?

    Dus bij dezen: hoe sla ik grotere blobs (> ~400MB) op in een SQL Server database dmv ADO?

    Ontwikkelomgeving: Delphi XE2 op Windows 8.1 met SQL Server 2014


    Greetz,

    Peter
    TMemoryLeak.Create(Nil);

  2. #2
    Persoonlijk nooit tegengekomen.
    Ik kwam wel, op de site waar je niet naartoe kunt linken, dit tegen.
    Maar in plaats van een PROCEDURE kun je natuurlijk ook gewoon die SQL uitvoeren in een loop in Delphi.
    Dus het moet met UPDATETEXT lukken.

    I was facing the same error while working for car manufacturer
    their database reaches 2TB and single technical document could
    have upto 40GB per file, I used this approach:

    1) setup stored procedures (whatever the names):

    SQL Code:
    1. CREATE PROCEDURE INSERT_FILE
    2. AS
    3.   BEGIN TRANSACTION
    4. /* insert  new file - simplified to for the problem; DATA is column holding your raw data */
    5.   INSERT FILES (DATA) VALUES ('')
    6. /* apply */
    7.   COMMIT
    8. /* return file index */
    9.   RETURN SCOPE_IDENTITY()
    10. GO
    SQL Code:
    1. CREATE PROCEDURE WRITE_BUFFER
    2.   @INDEX int,
    3.   @OFFSET int,
    4.   @LENGTH int,
    5.   @BUFFER image
    6. AS
    7.   BEGIN TRANSACTION
    8. /* write data buffer */
    9.   declare @POINTER BINARY(16)
    10.   SELECT @POINTER=TEXTPTR(DATA)  FROM FILES WHERE INDEX=@INDEX
    11.   updatetext FILES.DATA @POINTER @OFFSET @LENGTH @BUFFER
    12. /* apply */
    13.   COMMIT
    14. GO

    2) I used this routine from Delphi (I stripped unneeded details because it was a commercial product):

    Delphi Code:
    1. const
    2.   maxBUFFERCACHE=64000;
    3.  
    4. procedure InsertFile(FileName:AnsiString);
    5. var
    6.   INDEX,AMOUNT,OFFSET,NEXT:Integer;
    7.   BUFFER:AnsiString;
    8.   INPUT:File;
    9. begin
    10.   try
    11.     AssignFile(INPUT,FileName);
    12.     Reset(INPUT,1);
    13.     OFFSET:=0;
    14.     AMOUNT:=System.FileSize(INPUT);
    15.     SetLength(BUFFER,maxBUFFERCACHE);
    16. // Insert file
    17.     INDEX:=DB_MODIFY('INSERT_FILE',[]); // executes stored procedure INSERT_FILE with no parameters and catch returning value to INDEX
    18. // Reading to buffer
    19.     repeat
    20.       if AMOUNT<maxBUFFERCACHE then
    21.       begin
    22.         SetLength(BUFFER,AMOUNT);
    23.         BlockRead(INPUT,BUFFER[1],AMOUNT);
    24.         NEXT:=AMOUNT;
    25.         AMOUNT:=0;
    26.       end else
    27.       begin
    28.         BlockRead(INPUT,BUFFER[1],maxBUFFERCACHE);
    29.         NEXT:=maxBUFFERCACHE;
    30.         Dec(AMOUNT,maxBUFFERCACHE);
    31.       end;
    32.       DB_MODIFY('WRITE_BUFFER',[INDEX,OFFSET,0,BUFFER]); // executes stored procedure WRITE_BUFFER with parameters (INDEX,OFFSET,0,BUFFER)
    33.       Inc(OFFSET,NEXT);
    34.     until AMOUNT<=0;
    35.   finally
    36.     CloseFile(INPUT);
    37.   end;
    38. end;

    I didn't provide the DB_MODIFY function because it's enormous big but it simply
    does execution of any stored procedure with defined parameters while returning
    index when needed and checking that everything is fine.

  3. #3
    Fornicatorus Formicidae VideoRipper's Avatar
    Join Date
    Mar 2005
    Location
    Vicus Saltus Orientalem
    Posts
    5,708
    Dat was inderdaad een van de resultaten die bij mijn zoektocht naar boven kwam.
    Uiteindelijk kreeg ik UPDATETEXT niet echt aan de praat, maar vond wel een andere oplossing.

    MS SQL kent de UPDATE [Veldnaam].WRITE() method die hiervoor te gebruiken is.
    Quote Originally Posted by MSDN
    .WRITE (expression, @Offset, @Length)
    Specifies that a section of the value of column_name is to be modified. expression replaces @Length units starting from @Offset of column_name. Only columns of varchar(max), nvarchar(max), or varbinary(max) can be specified with this clause. column_name cannot be NULL and cannot be qualified with a table name or table alias.

    expression is the value that is copied to column_name. expression must evaluate to or be able to be implicitly cast to the column_name type. If expression is set to NULL, @Length is ignored, and the value in column_name is truncated at the specified @Offset.

    @Offset is the starting point in the value of column_name at which expression is written. @Offset is a zero-based ordinal position, is bigint, and cannot be a negative number. If @Offset is NULL, the update operation appends expression at the end of the existing column_name value and @Length is ignored. If @Offset is greater than the length of the column_name value, the Database Engine returns an error. If @Offset plus @Length exceeds the end of the underlying value in the column, the deletion occurs up to the last character of the value. If @Offset plus LEN(expression) is greater than the underlying declared size, an error is raised.

    @Length is the length of the section in the column, starting from @Offset, that is replaced by expression. @Length is bigint and cannot be a negative number. If @Length is NULL, the update operation removes all data from @Offset to the end of the column_name value.
    Met .WRITE kun je de data van een veld aanpassen (en toevoegen), dus ik kwam met deze
    vrij eenvoudige oplossing (in drie stappen):
    • Voeg een nieuw record toe, waarbij je Data of leeg laat (indien NULL-waarden zijn toegestaan)
      of je stopt er eerst een beperkt aantal ("BufferSize") bytes in:
      SQL Code:
      1. INSERT INTO Bestanden (Naam, DATA)
      2. VALUES (:Naam, :DATA)
      Indien BestandsGrootte <= BufferSize dan stoppen we gewoon, omdat alles al weggeschreven is.
    • Haal de ID op van het laatst toegevoegde record:
      SQL Code:
      1. SELECT @@IDENTITY AS LastID
    • Voeg de nog resterende data toe aan het veld:
      SQL Code:
      1. UPDATE Bestanden
      2. SET DATA.WRITE(:DATA, NULL, NULL)
      3. WHERE BestandID = :BestandID
    Het toevoegen in chunks op deze manier gaat prima, maar ik heb geen tijd meer om het uitgebreid
    te testen (tijd = geld) en uiteindelijk gaan we gewoon voor de "Normale" manier, aangezien het hoogst
    onwaarschijnlijk is dat er bestanden zullen worden opgeslagen die groter zijn dan ~200MB.
    Uit de tests die ik gedaan heb lijkt het niet echt snel te zijn, maar dat kan ook komen omdat ik zaken
    zoals buffergrootte e.d. niet getweaked heb.

    In ieder geval bedankt voor je hulp, @Rik!

    Greetz,

    Peter.
    Last edited by VideoRipper; 24-Jun-16 at 17:13.
    TMemoryLeak.Create(Nil);

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •