gennaio 2008 - Posts

SQL 2008: FILESTREAM
29 gennaio 08 05.07 | abenedetti | 6 comment(s)

Una delle novità più interessanti di SQL Server 2008 è la possibilità di utilizzare il file system per fare storage dei nostri dati.
Ovvero utilizzare il nuovo tipo di dati FILESTREAM per memorizzare large binary data come parte integrante del nostro database con la stessa consistenza transazionale!

SQL Server 2008 fornisce, infatti, il nuovo tipo di storage: VARBINARY(MAX) FILESTREAM.

Le applicazioni sono quindi in grado di utilizzare le "normali" istruzioni T-SQL SELECT/INSERT/UPDATE/DELETE per interrogare e modificare i dati FILESTREAM.

La cosa che forse potrebbe interessare ancora di più è che la versione express mantiene il limite (per database) dimensionale di 4Gb, supporta l'utilizzo di FILESTREAM ma, attenzione, attenzione, il size limit *non* include il FILESTREAM data container!

Vantaggi

  • fare storage di BLOB su file system, su filegroup dedicato, in contesto transazionale
  • accessibile attraverso T-SQL e streaming API NTFS (ottime performance!)
  • limite di storage = limite del volume NTFS
  • gestione integrata in SQL Server

Svantaggi / Punti di attenzione

  • non posso configurare il db mirroring su db con dati FILESTREAM
  • snapshot del db non fanno foto dei dati FILESTREAM
  • i dati FILESTREAM non possono essere cifrati nativamente da SQL Server
  • non è supportato su tabelle che utilizzano table-valued parameters

Vediamo in pratica...

Tre sono le cose che dobbiamo fare:

1. abilitare FILESTREAM sull'istanza
2. creare un db ed una tabella per memorizzare dati FILESTREAM
3. utilizzare i dati :-)

Per prima cosa andiamo ad abilitarne l'utilizzo. Prima di farlo visualizzo però, su shell DOS, il comando NET SHARE, questo il risultato:

image

use Master
go

EXEC sp_filestream_configure
       @enable_level = 3,
       @share_name = "myStorageFileStream";

/*
@enable_level = livello 3

Ovvero:
Enabled for Transact-SQL, local file system access, and remote file system access

Gli altri valori:

0 = off; 1 = T-SQL; 2 = T-SQL + File
*/

Comando NET SHARE:

image

Creo il database:

USE Master
GO

CREATE DATABASE myDocumentDB ON PRIMARY
  ( NAME = myDocumentDB_data,
    FILENAME = N'C:\data\myDocumentDB_data.mdf',
    SIZE = 10MB,
    MAXSIZE = 50MB,
    FILEGROWTH = 10%),
FILEGROUP myDocumentDBFileStream CONTAINS FILESTREAM
  ( NAME = myDocumentDBCatalog,
    FILENAME = N'C:\data\myDocumentDBCatalog')
LOG ON
  ( NAME = 'myDocumentDB_log',
    FILENAME = N'C:\data\myDocumentDB_log.ldf',
    SIZE = 5MB,
    MAXSIZE = 25MB,
    FILEGROWTH = 5MB
    );

Fate attenzione al filegroup "CONTAINS FILESTREAM", necessario per fare storage del tipo.

Sul file system avremo quindi:

image

Creo la tabella che andrà a contenere i dati FILESTREAM:

USE myDocumentDB
GO

CREATE TABLE dbo.catalog
(     
       idRecord int primary key identity(1,1),
       CatalogName varchar(100),
       Catalog varbinary(max) FILESTREAM,
       CatalogID UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL UNIQUE
);
GO

Attenzione alla presenza di una colonna UNIQUEIDENTIFIER con ROWGUIDCOL (è indispensabile!).

/* semplici insert di prova */
INSERT dbo.catalog (catalogName, catalog, catalogID)
    VALUES ('test catalog 1', CAST('my text ...' AS VARBINARY(MAX)), newid());
INSERT dbo.catalog (catalogName, catalog, catalogID)
    VALUES ('test catalog 2', CAST('my text ...' AS VARBINARY(MAX)), newid());
INSERT dbo.catalog (catalogName, catalog, catalogID)
    VALUES ('test catalog 3', CAST('my text ...' AS VARBINARY(MAX)), newid());
/* vedo la tabella */
select * from dbo.catalog

image

 

Se volessimo scrivere due righe di codice (C#) e provare ad utilizzare l'API OpenSqlFilestream, potremmo fare un'applicazione console simile a:

using System;
using System.Data.SqlClient;
using System.IO;
using System.Data;
using System.Data.SqlTypes;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;  

namespace consoleFileStreamSQL2008Test
{
    class Program
    {
        const int blockSize = 1024 * 512;

        static void Main()
        {
            /* !!! impostazione parametri per i file !!! */
            String filePath = @"C:\test\";
            String fileName = @"immagine.jpg";
            /* !!! impostazione parametri per i file !!! */

            /* !!! impostazione parametri per db !!! */
            SqlConnectionStringBuilder mySQLConnectionString = new SqlConnectionStringBuilder();
            mySQLConnectionString["Data Source"] = "TORONTO\\SQL008";
            mySQLConnectionString["Integrated Security"] = true;
            mySQLConnectionString["Initial Catalog"] = "myDocumentDB";
            /* !!! impostazione parametri per db !!! */

            Console.WriteLine("Apertura connessione SQL Server");
            SqlConnection mySQLConnection = new SqlConnection(mySQLConnectionString.ConnectionString);
            mySQLConnection.Open();

            Console.WriteLine("Apertura transazione");
            /* Start della transazione */
            SqlTransaction mySQLTransaction = mySQLConnection.BeginTransaction();

            /* Creo il GUID che verrà utilizzato per l'insert */
            Guid catalogID = Guid.NewGuid();
            Console.WriteLine("GUID: " + catalogID.ToString());

            /* Inserisco una riga */
            Console.WriteLine("Insert riga");
            SqlCommand cmd = new SqlCommand("INSERT dbo.catalog (catalogName, catalogID, catalog) VALUES( @fileName, @catalogID, CAST('.' AS VARBINARY(MAX)));", mySQLConnection, mySQLTransaction);

            cmd.Parameters.Add("@fileName", SqlDbType.VarChar, 100).Value = fileName;
            cmd.Parameters.Add("@catalogID", SqlDbType.UniqueIdentifier).Value = catalogID;
            cmd.ExecuteNonQuery();

            cmd = new SqlCommand("SELECT catalog.PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT(), catalogName FROM dbo.catalog WHERE [catalogID] = @catalogID;", mySQLConnection, mySQLTransaction);
            cmd.Parameters.Add("@catalogID", SqlDbType.UniqueIdentifier).Value = catalogID;

            SqlDataReader dr;
            dr = cmd.ExecuteReader(CommandBehavior.SingleRow);
            dr.Read();
            SqlString sqlFilePath = dr.GetSqlString(0);
            SqlBinary transactionToken = dr.GetSqlBinary(1);
            SqlString catalogName = dr.GetSqlString(2);
            dr.Close();

            /* Recupero un file handle per poter scrivere il filestream */
            SafeFileHandle handle = OpenSqlFilestream(
                    sqlFilePath.Value,
                    DESIRED_ACCESS_WRITE,
                    0,
                    transactionToken.Value,
                    (UInt32)transactionToken.Value.Length,
                    new LARGE_INTEGER_SQL(0));

            /* Apertura dello stream - destinazione e sorgente */
            FileStream destBlob = new FileStream(handle, FileAccess.Write);
            FileStream sourceFile = new FileStream(String.Concat(filePath, fileName), FileMode.Open, FileAccess.Read);

            byte[] buffer = new byte[blockSize];
            int bytesRead;
            while ((bytesRead = sourceFile.Read(buffer, 0, buffer.Length)) > 0)
            {
                destBlob.Write(buffer, 0, bytesRead);
            }

            /* Commit transazione e chiusura */
            Console.WriteLine("Commit e chiusure");
            destBlob.Close();
            sourceFile.Close();
            mySQLTransaction.Commit();
            mySQLConnection.Close();

            Console.WriteLine("Operazioni terminate.");
            Console.ReadLine();
        }

        public const UInt32 DESIRED_ACCESS_READ = 0x00000000;
        public const UInt32 DESIRED_ACCESS_WRITE = 0x00000001;
        public const UInt32 DESIRED_ACCESS_READWRITE = 0x00000002;

        [DllImport("sqlncli10.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern SafeFileHandle OpenSqlFilestream(
                    string FilestreamPath,
                    UInt32 DesiredAccess,
                    UInt32 OpenOptions,
                    byte[] FilestreamTransactionContext,
                    UInt32 FilestreamTransactionContextLength,
                    LARGE_INTEGER_SQL AllocationSize);

        [StructLayout(LayoutKind.Sequential)]
        public struct LARGE_INTEGER_SQL
        {
            public Int64 QuadPart;
            public LARGE_INTEGER_SQL(Int64 quadPart) { QuadPart = quadPart; }
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern UInt32 GetLastError();
    }
}
 

Curiosità: ad oggi, tramite la stored procedure sp_helpdb il filegroup relativo al FILESTREAM non è ancora visibile...

image

Ultima cosa: su sistemi operativi Windows Vista o Windows XP Service Pack 2, se si utilizza il livello di accesso 2 or 3, la porta SMB (server message block) deve essere aperta nel firewall. Vedi "sp_filestream_configure" nei Books Online di SQL Server 2008.

Come al solito tenete d'occhio codeplex, ad esempio qui.

Filed under:
[SQL 2005] Bakcup & database di sistema
10 gennaio 08 12.29 | abenedetti | 1 comment(s)

Domanda facile: quanti sono i database di sistema in SQL Server 2005?

Chi ha risposto 4 ?!?! :-)

Sono 5! Oltre ai noti MASTER, MODEL, MSDB, TEMPDB (se utilizzassimo le repliche avremmo anche DISTRIBUTION), è stato aggiunto il RESOURCE database.

All'interno della cartella DATA della nostra istanza abbiamo infatti due file che prima non eravamo abituati a vedere:

  • mssqlsystemresource.mdf
  • mssqlsystemresource.ldf
image

Questo db (che, nascosto ed in read-only, non è nè enumerabile, nè sfogliabile) è assolutamente vitale per SQL Server, al punto che il servizio non è in grado di partire se non lo ha a disposizione.

Cosa succede se dovessi avere un problema sui settori del disco dove si trovano proprio i files del db (oppure il nostro collega che, non conoscendolo, lo crede una qualche prova e pensa bene di cancellarlo) ? L'istanza *non parte* !

Simuliamo il problema: fermo (stoppo) il servizio, rinomino i file e, se provo a far ripartire l'istanza ottengo un bell'errore "on the face":

image

Sull'event viewer posso leggere:

image 

Il problema principale è che, per scelta, non viene consentito di farne il backup e, tantomeno, il restore.

La tecnica per recuperarlo è quindi differente rispetto alle tecniche che possiamo utilizzare per gli altri db di sistema: non potendo in alcun modo farne un salvataggio, l'unica possibilità che abbiamo è quella di (ri)copiarlo nella directory DATA.

 

La cosa da sottolineare è che (come può avvenire sempre) un service pack o anche una fix potrebbe modificarne gli oggetti (e quindi modificarne il numero di versione).

image

 

Il suggerimento è quindi quello di tenere copia di questi file di database per poter essere in grado di ripristinare il corretto funzionamento dell'istanza in caso di failure.

 

E se voglio metterci il naso?

Posso fermare il servizio, faccio una copia dei file:

image

quindi eseguo una CREATE DATABASE sulla copia dei miei file, qualcosa come:

 

USE [master]
GO
CREATE DATABASE [myMSSQLSystemResource] ON
( FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data\mssqlsystemresource_Copy.mdf' ),
( FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data\mssqlsystemresource_Copy.ldf' )
    FOR ATTACH
GO

 

A questo punto, posso anche "spingermi" ad utilizzare tabelle di sistema per recuperare un elenco di tutti gli oggetti (ben 2738 !!!) contenuti al suo interno:

 

USE [myMSSQLSystemResource]
GO

SELECT distinct
        sysObjects.Name [Obj name],
        CASE encrypted
          WHEN 1 THEN 'Yes'
          ELSE 'No'
        END [Obj Encrypted],
        CASE WHEN sysObjects.xType = 'C' THEN 'Check Constraint'
             WHEN sysObjects.xType = 'D' THEN 'Default constraint'
             WHEN sysObjects.xType = 'F' THEN 'FK constraint'
             WHEN sysObjects.xType = 'L' THEN 'Log'
             WHEN sysObjects.xType = 'FN' THEN 'Scale Function'
             WHEN sysObjects.xType = 'IF' THEN 'Inline Function'
             WHEN sysObjects.xType = 'P' THEN 'Stored Procedure'
             WHEN sysObjects.xType = 'PK' THEN 'PK constraint'
             WHEN sysObjects.xType = 'RF' THEN 'Replication Filer'
             WHEN sysObjects.xType = 'S' THEN 'System Table'
             WHEN sysObjects.xType = 'TF' THEN 'Table Function'
             WHEN sysObjects.xType = 'TR' THEN 'Trigger'
             WHEN sysObjects.xType = 'U' THEN 'User Table'
             WHEN sysObjects.xType = 'UQ' THEN 'Unique constraint'
             WHEN sysObjects.xType = 'V' THEN 'View'
             WHEN sysObjects.xType = 'X' THEN 'Extended SP'
             ELSE 'Other'
        END AS [Obj Type]
FROM    sysObjects
        LEFT OUTER JOIN syscomments ON sysObjects.ID = syscomments.ID
ORDER BY [Obj Type],
        [Obj name]

 

PS: una corretta strategia di backup *deve* tenere conto anche di questo "nuovo" database

PS2: una corretta strategia di backup *deve* essere provata!

Filed under:
SLQ Server on Channel9
04 gennaio 08 12.40 | abenedetti | with no comments

Mi è sempre piaciuto Channel9, ora ancora di più :-)

Qui ha trovato casa SQL Server

Filed under:
MVP: Renewed
02 gennaio 08 09.43 | abenedetti | 4 comment(s)

L'anno inizia bene con una mail di Microsoft che mi conferma MVP per SQL Server, ovvero mi rinnova anche per quest'anno.

Certo in questo momento il termometro segna 38 e 7... ma le mail riesco ancora a leggerle :-)

Filed under:

This Blog

Syndication