Zur Hauptseite ... Zum Onlinearchiv ... Zum Abonnement ... Zum Newsletter ... Zu den Tools ... Zum Impressum ... Zum Login ...

Achtung: Dies ist nicht der vollständige Artikel, sondern nur ein paar Seiten davon. Wenn Sie hier nicht erfahren, was Sie wissen möchten, finden Sie am Ende Informationen darüber, wie Sie den ganzen Artikel lesen können.

Kompletten Artikel lesen?

Einfach für den Newsletter anmelden, dann lesen Sie schon in einer Minute den kompletten Artikel und erhalten die Beispieldatenbanken.

E-Mail:

Gedrucktes Heft

Diesen Beitrag finden Sie in Ausgabe 1/2018.

Unser Angebot für Sie!

Lesen Sie diesen Beitrag und 500 andere sofort im Onlinearchiv, und erhalten Sie alle zwei Monate brandheißes Access-Know-how auf 72 gedruckten Seiten! Plus attraktive Präsente, zum Beispiel das bald erscheinende Buch 'Access 2010 - Das Grundlagenbuch für Entwickler'!

Diesen Beitrag twittern

Datenkompression leicht gemacht

Wenn Sie längere Texte in Tabellen abspeichern oder umfangreiche Binärdaten in OLE-Feldern ablegen, so bläht das die Datenbank auf. Während dieser Umstand bei lokalen Datenbanken heutzutage kein Problem mehr darstellt, sieht die Sache in einer Mehrbenutzerumgebung und Netzwerkzugriffen auf Backends schon anders aus. Hier sollte der Traffic minimiert werden. Da macht sich dann die Komprimierung solcher Datenfelder gut, was zu verbesserter Performance führen kann.

Komprimieren von Memo- und Long-Binary-Feldern

Über das Für und Wider von in Tabellen abgelegten Binärdaten oder Textdokumente möchten wir hier nicht diskutieren. Das kann ganz einfach immer wieder vorkommen, und es gibt ganze Serverumgebungen, die davon weidlich Gebrauch machen, wie etwa der Microsoft Sharepoint-Server. Die Kompression dieser Daten verkleinert die Datenbankdateien und hilft dabei den benötigten Traffic zu den Clients zu verringern.

An sich ist derlei bereits in Access integriert. Der Datentyp Anlage (Attachment) speichert binäre Daten und komprimiert diese nach Bedarf, wobei Microsoft sich weitgehend darüber ausschweigt, bei welchen Dateitypen dies zutrifft und welcher Kompressionsalgorithmus zum Einsatz kommt. Nicht jeder ist jedoch ein Freund von Anlage-Feldern, denn aufgrund des dahinter liegenden versteckten Datenmodells, welches sich tatsächlich von Sharepoint-Technik ableitet, ist der programmtechnische Umgang mit ihnen recht komplex. Zudem können Anlagefelder nicht in Access 2003 oder früher angezeigt oder ausgelesen werden. Deshalb macht sich die Binärspeicherung in OLE-Feldern, bei denen es sich ja schlicht um Long-Binary-Felder handelt, besser.

Das Thema ist nicht neu. Bereits in der Ausgabe 3/2007 (Shortlink 466) von Access Im Unternehmen wurde es behandelt, weshalb wir an dieser Stelle zusätzlich auf jenen Beitrag verweisen. Dort wurde die Komprimierung über eine externe Komponente erreicht, eine zlib-DLL, auf die im VBA-Projekt verwiesen wird. Darauf kann verzichtet werden, wenn man sich der wenig beachteten Windows-API-Funktion RTLCompressBuffer bedient, die eigentlich zur Abteilung der Treiber-APIs gehört. Sie erreicht, vor allem bei Texten, durchaus vergleichbare Kompressionsraten. Der eingesetzte Algorithmus ist eine LZ-Variante (Lempel-Ziv).

Einschub: Kompression in Anlagefeldern

Da Microsoft keine komplette Liste veröffentlicht, welche Dateitypen in Anlage-Feldern denn nun komprimiert werden und welche nicht, sondern pauschal behauptet, dass jene unbearbeitet blieben, die bereits hinlänglich komprimiert sind, wie etwa JPG, haben wir einen einfachen Test unternommen. Eine Datenbank enthält eine Tabelle, die wiederum nur ein Anlage-Feld enthält. Wir messen die Dateigröße dieser ACCDB nach dem Hinzufügen jeweils einer größeren Datei eines bestimmten Typs und anschließendem Komprimieren und Reparieren der Datenbank. Verwendet werden gängige Dateitypen. Steigt die Größe der ACCDB um die Größe der hinzugefügten Datei, so dürfte die interne Kompression der Anlage nicht angesprochen haben. Die folgende Tabelle gibt Auskunft über das Ergebnis:

<b>Dateityp	Größe Datei	Größe DB	Kompression, ca.
(leer)	-		404 kB	-
txt	3770 kB		1020 kB	6-fach
doc	3430 kB		1778 kB	2,5-fach
rtf	4400 kB		524 kB	37-fach
xls	3640 kB		1020 kB	6-fach
ppt	3800 kB		676 kB	14-fach
pdf	3280 kB		2208 kB	1,8-fach
bmp	4130 kB		516 kB	34-fach
jpg	4300 kB		4752 kB	keine
tiff 	3050 kB		3305 kB	keine
xml	3830 kB		992 kB	6,5-fach
kml	6650 kB		540 kB	49-fach
csv	3380 kB		740 kB	10-fach

Zu erwähnen wäre noch, dass dezidiert Dateien verwendet wurden, die entweder viele Wiederholungen enthalten oder viele Null-Bytes, sich mithin also gut komprimieren lassen. Das Ergebnis ist eindeutig: Die Kompression scheint zum Glück der Default zu sein. Nur ausgewählte Dateiformate, wie JPG, ZIP, DOCX, XLSX, die bekannterweise schon eine Kompression aufweisen, werden ausgenommen. Erstaunlich hoch sind die Kompressionsraten. Dass etwa eine RTF-Datei von über 4 MB Größe auf intern 120 kB schrumpft, war nicht zu erwarten. Als Datei-Container eignen sich Anlage-Felder demnach hervorragend.

Leider kann der in diesem Beitrag vorgestellte Algorithmus da nicht mithalten. Dennoch hat er seine Existenzberechtigung, und wir führen kurz die Vorteile gegenüber Attachments auf:

  • Die komprimierte Speicherung ist nicht nur auf Dateien beschränkt, sondern kann etwa auch auf beliebige per VBA generierte Strings angewandt werden.
  • Die Speicherung in OLE-Feldern kann auch in Access 2003 und früher erfolgen.
  • Auch EXE-Dateien können integriert werden, was Access bei Anlage-Feldern verbietet.
  • OLE-Felder lassen sich über ODBC ansprechen und deren Inhalte sind damit auch für SQL-Server oder andere DBMS nicht tabu.
  • Der Umgang mit Long-Binary-Feldern ist unter VBA und DAO, sowie ADODB, erheblich einfacher, als mit Attachments.

Kompressionsroutine

Das Modul mdlCompression der Beispieldatenbank enthält eine Funktion CompressRTL, die Byte-Arrays komprimieren kann. Ihr Code ist in Listing 1 abgebildet. Er nutzt einige Windows-API-Funktionen, die im Modulkopf deklariert sind (Listing 2). Der Rückgabewert ist wiederum ein Byte-Array mit den komprimierten Daten. Das Ganze kommt ausgesprochen übersichtlich daher. Auf die genaue Funktion des Codes und der API-Aufrufe möchten wir hier gar nicht eingehen. Einige Hinweise dürfen jedoch nicht fehlen.

Private Declare Function RtlGetCompressionWorkSpaceSize _
     Lib "NTDLL" (ByVal Flags As Integer, WorkSpaceSize As _
     Long, UNKNOWN_PARAMETER As Long) As Long
Private Declare Function NtAllocateVirtualMemory Lib _
     "ntdll.dll" (ByVal ProcHandle As Long, BaseAddress As _
     Long, ByVal NumBits As Long, regionsize As Long, ByVal _
     Flags As Long, ByVal ProtectMode As Long) As Long
Private Declare Function RtlCompressBuffer Lib "NTDLL" _
     (ByVal Flags As Integer, ByVal BuffUnCompressed As Long, _
     ByVal UnCompSize As Long, ByVal BuffCompressed As Long, _
     ByVal CompBuffSize As Long, ByVal UNKNOWN_PARAMETER As _
     Long, OutputSize As Long, ByVal WorkSpace As Long) As _
     Long
Private Declare Function RtlDecompressBuffer Lib _
     "NTDLL" (ByVal Flags As Integer, ByVal BuffUnCompressed _
     As Long, ByVal UnCompSize As Long, ByVal BuffCompressed _
     As Long, ByVal CompBuffSize As Long, OutputSize As _
     Long) As Long
Private Declare Function NtFreeVirtualMemory Lib _
     "ntdll.dll" (ByVal ProcHandle As Long, BaseAddress As _
     Long, regionsize As Long, ByVal Flags As Long) As Long

Listing 1: Die fünf im Modul verwendeten API-Funktionen

So enthält die Prozedur keine Fehlerbehandlung. Übergeben Sie etwa ein undimensioniertes Array, so kracht es gleich in der ersten Zeile beim Ausdruck UBound. Auch sonst werden die Rückgabewerte der API-Funktionen nicht ausgewertet, so dass Fehlfunktionen nicht offensichtlich sind. Allerdings dürfte es auch kaum einen Fall geben, der hier bei einem ordnungsgemäßen Byte-Array zu Fehlern führt. Kritisch ist bestenfalls der Wert der von RTLCompressBuffer zurückgelieferten Variablen retSize, die angibt, wie viele Bytes im Ergebnis enthalten sind. Er wird im weiteren Verlauf dazu verwendet, um das Ergebnis-Array binOut neu zu dimensionieren. Hier könnten rein theoretisch Werte anfallen, die über den erlaubten Wertebereich hinausgehen. Uns ist bei allen Tests derlei aber nicht untergekommen.

Nun möchten Sie möglicherweise nicht Byte-Arrays komprimieren, sondern Strings. Das macht eine Konvertierung dieser erforderlich. Gegeben sei etwa ein Text in der Variablen sText, der zu komprimieren wäre. Dann verwenden Sie die VBA-Funktion StrConv:

Dim bin() As Byte
Dim binRet() As Byte
bin = StrConv(sText, vbFromUnicode)
binRet = CompressRTL(bin)

StrConv kann Strings in Byte-Arrays überführen und umgekehrt. Da es sich bei VBA-Strings immer um Unicode handelt, ist der Parameter vbFromUnicode der richtige. Er macht aus dem String quasi ein ANSI-Byte-Array. Dieses übergeben Sie dann der Funktion CompressRTL des Moduls.

Dekompressionsroutine

Mit dem komprimierten Byte-Array selbst können Sie wenig anfangen. Entweder speichern Sie es in einer Datei ab, oder im OLE-Feld einer Tabelle. Es reicht dazu schlicht die Zuweisung an den Feldinhalt. Beispiel:

Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset("Binaertabelle")
rs.Edit	'oder rs.AddNew
rs!OleFeld1.Value = binRet
rs.Update

Einstmals mussten Sie sich mit der Methode AppendChunk eines DAO-Felds herumschlagen und in einer Schleife Teile des Byte-Arrays (Chunks) hinzufügen. Das ist veraltet. Die unmittelbare Zuweisung funktioniert bei heutigen Rechnerausstattungen auch bei großen Byte-Arrays. Das Limit liegt lediglich beim dem DAO-Thread zugewiesenen RAM-Bereich (Heap Working Set) der Datenbank-Engine.

Nun möchten Sie die komprimierten Daten wieder in ihrer ursprünglichen Form zurückgewinnen. Das übernimmt die Dekompressionsroutine DecompressRTL des Moduls, welche in Listing 3 abgebildet ist. Auch ihr Umfang ist erfreulich gering. Ihr Aufruf entspricht genau dem für die Komprimierprozedur CompressRTL. Sie holen sich etwa die komprimierten Daten aus der Tabelle und erhalten die entpackten im Rückgabe-Array, welches Sie wieder per StrConv in einen Text verwandeln:

Public Function DeCompressRTL(Buffer() As Byte) As Byte()
     Dim LSize As Long, memSize As Long
     Dim binOut() As Byte
     
     LSize = UBound(Buffer) * 12.5
     ReDim binOut(LSize)
     RtlDecompressBuffer 2& Or &H100, VarPtr(binOut(0)), LSize, _
         VarPtr(Buffer(0)), 1& + UBound(Buffer), memSize
     If (memSize > 0) And (memSize <= UBound(binOut)) And _
        (memSize < 2000000) Then
         ReDim Preserve binOut(memSize - 1)

Sie haben das Ende des frei verfügbaren Teils des Artikels erreicht. Lesen Sie weiter, um zu erfahren, wie Sie den vollständigen Artikel lesen und auf viele hundert weitere Artikel zugreifen können.

Sind Sie Abonnent?Jetzt einloggen ...
 

Kompletten Artikel lesen?

Einfach für den Newsletter anmelden, dann lesen Sie schon in einer Minute den kompletten Artikel und erhalten die Beispieldatenbanken.

Bitte teilen Sie uns Ihre Anrede, Ihren Namen und Ihre E-Mail-Adresse mit:

Anrede:
Vorname:
Nachname:
E-Mail:

© 2003-2018 André Minhorst Alle Rechte vorbehalten.