Podczas pracy nad wydaniem dziennika transakcji dbForge, między innymi, nasz zespół musiał zastanowić się, jak prawidłowo przechowywać wpisane dane XML.
Na początek warto wspomnieć, że SQL Server nie przechowuje XML w formacie, w jakim został wprowadzony. Ciąg XML jest analizowany, dzielony na znaczniki, a zatem jest przechowywany w skompresowanym formacie. Elementy opisu, które serwer uważa za niepotrzebne, są odrzucane.
Należy również pamiętać, że jeśli typ danych kolumny jest określony jako prosty XML, serwer będzie przechowywać te dane jako ciągi Unicode.
Przykład 1.
CREATE TABLE XmlValuesTable ( [uid] [int] IDENTITY PRIMARY KEY, v XML NOT NULL ); GO INSERT INTO XmlValuesTable (v) VALUES ('<note><float>123.456</float><time>01:23:45.789</time></note>'); INSERT INTO XmlValuesTable (v) VALUES ('<note><float>4.0000000000</float><time>01:23:45Z</time></note>');
Serwer będzie przechowywać wstawkę dane w następujący sposób:
F0 04 6E006F0074006500 <- Name "note" EF 000001 <- Namespace 01 F8 01 <- tag 01 F0 05 66006C006F0061007400 <- Name "float" EF 000002 <- Namespace 02 F8 02 <- tag 02 11 07 3100320033002E00340035003600 <- string "123.456" F7 <- closing tag F0 04 740069006D006500 <- Name "time" EF 000003 <- Namespace 02 F8 03 <- tag 03 11 0C 300031003A00320033003A00340035002E00370038003900 <- string "01:23:45.789" F7 <- closing tag F7 <- closing tag
W poniższym przykładzie typ danych kolumny jest określony jako wpisany przez XML Schema Collection.
Przykład 2.
CREATE XML SCHEMA COLLECTION [XmlValuesSchemaCollection_datetime2] AS '<?xml version="1.0"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:sqltypes="http://schemas.microsoft.com/sqlserver/2004/sqltypes" <xsd:element name="datetime2" type="sqltypes:datetime2"/> </xsd:schema>'; GO CREATE TABLE XmlValuesTable_datetime2 ( [uid] [int] IDENTITY PRIMARY KEY, v XML(XmlValuesSchemaCollection_datetime2) NOT NULL ); GO INSERT INTO XmlValuesTable_datetime2 (v) VALUES (N'<datetime2>2014-06-18T06:39:05.190</datetime2>'); GO
W tym konkretnym przypadku serwer przechowa wstawkę dane w następujący sposób:
EA 09 014C010015 1A000000 <- type info 0x14C (332) “datetime2”, 0x15 (21) “dateTime” + offset F0 09 6400610074006500740069006D0065003200 <- Name "datetime2" EF 000001 <- Namespace 01 F8 01 <- tag 01 EA 05 004C010015 <- type info 7E 02978924A9380B <- "2014-06-18T06:39:05.190" F7 <- closing tag
W ten sposób serwer konwertuje przechowywane dane na typy określone w dodatku do tego artykułu (listę wszystkich typów danych można zobaczyć, uruchamiając na serwerze zapytanie „select * from sys.xml_schema_types”).
Przyjrzyjmy się, jak serwer zapisze bardziej złożoną strukturę, podobną do tej w Przykładzie 1 i opisanej za pomocą XML Schema Collection.
Przykład 3.
CREATE XML SCHEMA COLLECTION [XmlValuesSchemaCollection] AS '<?xml version="1.0"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:sqltypes="http://schemas.microsoft.com/sqlserver/2004/sqltypes" attributeFormDefault="unqualified" elementFormDefault="qualified"> <xsd:import namespace="http://schemas.microsoft.com/sqlserver/2004/sqltypes" schemaLocation="http://schemas.microsoft.com/sqlserver/2004/sqltypes/sql2008/sqltypes.xsd"/> <xsd:element name="note"> <xsd:complexType> <xsd:sequence> <xsd:element name="float" type="xsd:float"/> <xsd:element name="time" type="xsd:time"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema>'; GO CREATE TABLE XmlValuesTable ( [uid] [int] IDENTITY PRIMARY KEY, v XML(XmlValuesSchemaCollection) NOT NULL ); GO INSERT INTO XmlValuesTable (v) VALUES ('<note><float>123.456</float><time>01:23:45.789</time></note>');
Serwer zapisze wstawkę dane w następujący sposób:
EA 05 0001000100 <- type info F0 04 6E006F0074006500 <- Name "note" EF 000001 <- Namespace F8 01 <- tag 01 EA 09 0111000011 12000000 <- type info 0x11 (17) "float" + offset F0 05 66006C006F0061007400 <- Name "float" EF 000002 <- Namespace F8 02 <- tag 02 EA 05 0011000011 <- type info 0x11 (17) "float" 03 79E9F642 <- "123.456" F7 <- closing tag EA 09 0116000016 10000000 <- type info 0x16 (22) "time" + offset F0 04 740069006D006500 <- Name "time" EF 000003 <- Namespace F8 03 <- tag 03 EA 05 0016000016 <- type info 0x16 (22) "time" 7D 03FDAF4C005B950A <- "01:23:45.789" F7 <- closing tag F7 <- closing tag
Spróbujmy dodać link do schematu do wstawki.
Przykład 4.
INSERT INTO XmlValuesTable (v) VALUES ('<note xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><float>123.456</float><time>01:23:45.789</time></note>');
EA 05 0001000100 <- type info F0 04 6E006F0074006500 <- Name "note" EF 000001 <- Namespace F8 01 <- tag 01 F0 09 78006D006C006E0073003A00780073006900 <- Name "xmlns:xsi" EF 000200 <- Namespace "xmlns:xsi" F6 02 <- Attribute 11 29 68007400740070003A002F002F007700770077002E00770033002E006F00720067002F0032003000300031002F0058004D004C0053006300680065006D0061002D0069006E007300740061006E0063006500 <- "http://www.w3.org/2001/XMLSchema-instance" F5 <- closing bracket EA 09 0111000011 12000000 <- type info 0x11 (17) "float" + offset F0 05 66006C006F0061007400 <- Name "float" EF 000003 <- Namespace F8 03 <- tag 03 EA 05 0011000011 <- type info 0x11 (17) "float" 03 79E9F642 <- "123.456" F7 <- closing tag EA 09 0116000016 10000000 <- type info 0x16 (22) "time" + offset F0 04 740069006D006500 <- Name "time" EF 000004 <- Namespace F8 04 <- tag 08 EA 05 0016000016 <- type info 0x16 (22) "time" 7D 03FDAF4C005B950A <- "01:23:45.789" F7 <- closing tag F7 <- closing tag
Jak widać, serwer starannie zapisał przestrzeń nazw jako atrybut i wykorzystał na to prawie połowę miejsca, nawet pomimo tego, że przestrzeń nazw nie służy tutaj żadnemu użytecznemu celowi – dane zostały zapisane w taki sam sposób, w jaki byłyby zapisane bez przestrzeni nazw.
Wniosek
Z powyższego może się wydawać, że można zmniejszyć rozmiar bazy danych, przechowując niektóre typy danych (np. float) jako wpisane wartości, ponieważ 4 bajty wymagają znacznie mniej pamięci niż ta sama wartość zapisana jako ciąg Unicode. Należy jednak pamiętać, że dla każdej wartości używane jest dodatkowe 7-18 bajtów, aby opisać jej typ i przenieść ją do wymaganej pozycji.
Uzupełnienie
Korelacja typów XML, typów podstawowych i typów danych, których serwer używa do przechowywania wpisanych wartości.
Typ XML | Typ podstawowy | Przechowywany jako typ | Rozmiar w bajtach | |
dowolny typ | ciąg | 2 * znaki | ||
anySimpleType | dowolny typ | ciąg | ||
ciąg | anySimpleType | ciąg | ||
wartość logiczna | anySimpleType | wartość logiczna | 1 | |
pływający | anySimpleType | pływający | 4 | |
podwójny | anySimpleType | podwójne | 8 | |
dziesiętny | anySimpleType | SqlDecimal | 20 | |
czas trwania | anySimpleType | ciąg | ||
dataGodzina | anySimpleType | *1 | ||
czas | anySimpleType | *1 | ||
data | anySimpleType | *1 | ||
gYearMonth | anySimpleType | ciąg | ||
gRok | anySimpleType | ciąg | ||
gMiesiącDzień | anySimpleType | ciąg | ||
gDzień | anySimpleType | ciąg | ||
gMiesiąc | anySimpleType | ciąg | ||
szesnastkowy binarny | anySimpleType | tablica bajtów | ||
base64Binary | anySimpleType | tablica bajtów | ||
dowolny URI | anySimpleType | ciąg | ||
QName | anySimpleType | ciąg | ||
znormalizowany ciąg | ciąg | ciąg | ||
token | ciąg | ciąg | ||
język | ciąg | ciąg | ||
Nazwa | ciąg | ciąg | ||
Nazwa NC | ciąg | ciąg | ||
ENTITY | ciąg | ciąg | ||
NMTOKEN | ciąg | ciąg | ||
liczba całkowita | dziesiętny | SqlDecimal | 20 | |
nonPositiveInteger | liczba całkowita | SqlDecimal | 20 | |
ujemna liczba całkowita | nonPositiveInteger | SqlDecimal | 20 | |
długi | liczba całkowita | SqlDecimal | 20 | |
int | długi | SqlDecimal | 20 | |
krótki | int | SqlDecimal | 20 | |
bajt | krótki | SqlDecimal | 20 | |
nonNegativeInteger | liczba całkowita | SqlDecimal | 20 | |
unsignedLong | nonNegativeInteger | SqlDecimal | 20 | |
unsignedInt | unsignedLong | SqlDecimal | 20 | |
unsignedShort | unsignedInt | SqlDecimal | 20 | |
unsignedByte | unsignedShort | SqlDecimal | 20 | |
dodatnia liczba całkowita | nonNegativeInteger | SqlDecimal | 20 | |
znak | ciąg | ciąg | ||
nchar | ciąg | ciąg | ||
varchar | ciąg | ciąg | ||
nvarchar | ciąg | ciąg | ||
tekst | ciąg | ciąg | ||
ntext | ciąg | ciąg | ||
zmienna | base64Binary | tablica bajtów | ||
binarny | base64Binary | tablica bajtów | ||
obraz | base64Binary | tablica bajtów | ||
sygnatura czasowa | base64Binary | tablica bajtów | ||
sygnatura czasowaNumeryczna | długi | SqlDecimal | 20 | |
liczbowe | dziesiętny | SqlDecimal | 20 | |
duży | długi | SqlDecimal | 20 | |
mały | krótki | SqlDecimal | 20 | |
tinyint | unsignedByte | SqlDecimal | 20 | |
bit | wartość logiczna | wartość logiczna | 1 | |
prawdziwe | pływający | pływający | 4 | |
data i godzina | dataGodzina | *1 | ||
smalldatetime | dataGodzina | *1 | ||
pieniądze | dziesiętny | SqlDecimal | ||
małe pieniądze | dziesiętny | SqlDecimal | ||
unikalny identyfikator | dziesiętny | ciąg | ||
data/godzina2 | dataGodzina | *1 | ||
przesunięcie daty i godziny | dataGodzina | *1 | ||
hierarchid | ciąg | ciąg | ||
obiektu | dowolny URI | ciąg |
*1 – informacja o dacie/czasie. Konkretny typ jest zdefiniowany przez wartość.
Wartość | Przechowywany jako typ | Rozmiar w bajtach |
Przesunięcie daty | Data (liczba dni) | 3 |
Przesunięcie daty (2019-09-16+02:00) | Przesunięcie daty i godziny | 11 |
Data i godzina | DataGodzina | 7-9 zależy od precyzji |
Przesunięcie daty i godziny | Przesunięcie daty i godziny | 9 |
Czas | DataGodzina | 7-9 zależy od precyzji |
Przesunięcie czasowe (01:23:45Z) | Przesunięcie daty i godziny | 9 |