• Consideraciones de seguridad para bases de datos de contenido “Contained Databases”

    Hola!!

    Una de las nuevas características de SQL Server 2012 son las Contained Databases , gracias a esta característica podemos parcialmente aislar algunos objetos que por su naturaleza residen en la instancia de bases de datos como lo son los “logins”, si bien en términos de migración y consolidación esta es una gran ventaja, permítanme comentarles algunos aspectos a considerar a nivel de seguridad.

    Se debe tener en consideración que si algún usuario en la contained database tiene el permiso de ALTER ANY USER,  tendrá la capacidad de modificar los permisos de los usuarios y dar permisos de conexión a cualquier persona sin conocimiento del Administrador de SQL, usuarios que pertenezca a los roles de bases de datos “db_owner” y “security_admin” obtendrán automáticamente dicho permiso, dando como resultado un hueco de seguridad, por esto tenemos que ser muy cuidadosos con dar estos permisos a usuarios no administradores de SQL.

    Otro escenario que debemos considerar es que cuando nos conectamos a un contained database y en esa misma instancia se encuentra alguna base de datos con la cuenta “guest” habilitada,  una vez que el usuario se ha podido conectar a la contained database, también tendrá la capacidad de acceder a esa base de datos, esto en conjunto que el escenario anterior donde una usuario con el permiso de ALTER ANY USER pude dar acceso a cualquier persona para conectarse al servidor, se convierta en un gran problema de seguridad. Es por ello que debemos minimizar el uso de este permiso y constantemente auditar el acceso y uso del permiso ALTER ANY USER en las bases de datos.

    Ahora platiquemos de otro escenario, ¿Que sucedería si creamos un usuario en nuestra base de datos de contenido, con el mismo nombre de un login existente en nuestra instancia? Si accedemos a la base por medio de este login y a su vez al momento de conectarnos especificamos la Contained Database como catálogo inicial, lo que obtendríamos sería una negación del servicio para este login, ya que el contexto de seguridad  evalúa usuario y contraseña sobre la Contained Database en lugar de el entrono de logins de SQL.

    Para finalizar y tomando en cuenta mis dos recomendaciones anteriores, les expongo lo siguiente. ¿Cual es el impacto de darle “attach” a una base de datos de contenido en una nueva instancia? , al attachar una Contained Database estaremos abriendo una puerta de conexión a los usuarios con password de la Contained Database attchada.  ¿Cómo podemos evitar el riesgo de acceso a usuarios no autorizados a esta instancia? usando la opción de RESTRICTED_USER, ya que esta previene la autenticación para usuarios con passwords de la Contained databases.

    Todas estas consideraciones son fácilmente manejables teniendo una adecuada delegación de control de acceso y mejores prácticas de seguridad.

    Espero les sean de utilidad estos puntos y para mayor información de este y tros escenarios pueden consultar en TechNet:

    http://technet.microsoft.com/en-us/library/ff929055

     

     

     

     

     

     

     

     

  • Recuperar acceso de sysadmin en SQL

    Bajen todos los servicios de SQL, incluyendo el SQL Engine, Analisys Services, Reporting Services, Integration Services, Full Text Search o cualquier otro que pueda hacer una sesión a SQL. También es bueno deshabilitar el TCP/IP protocol en caso que alguna aplicación se esté conectando a SQL server remotamente y haga sesión.

    Ahora agreguen el switch –m (se debe agregar “;-m” sin espacios, ni las comillas) en los parámetros de inicio de SQL, esto hará iniciar el Servicio de SQL Server en modo single-user y solo podrá accederlo un solo usuario (es por esto que deben para cualquier servicio que haga sesión a SQL) y además debe tener privilegios de administrador en la maquina Windows

    Luego de esto deberán ingresar a SQL usando sqlcmd en modo trusted podrán entrar a SQL Server, recuerden que deben estar logueados con una cuenta que sea miembro de los Administradores locales del Servidor

    sqlcmd –E –S NombreServidor\NombreInstancia

    Luego de entrar a sqlcmd podrá ejecutar cualquier comando que desee, ya que entrar con el contexto de un sysadmin, para colocar a alguno login como sysadmin podrán usar el stored procedures sp_addsrvrolemember: EXEC sp_addsrvrolemember 'NombreLogin', ‘sysadmin’; GO
     

    Ya con esto tendrá un Login con privilegios de sysadmin, ahora ya puede bajar el servicio de SQL, remover el swith –m (esto remueve el modo single-user) e iniciar SQL Server, podrá usar el login al que se le otorgo los derechos de sysadmin y hacer los cambios que requieran.

  • COLUMN STORE INDEXES

     

    Some days ago I visit a client on Puerto Rico that was having some performance problems on a transactional application and after resolving the issue the client express interested on learning about Column Store Index, this kind of indexes is a new functionality on SQL Server 2012, as we will see his kind of index work great on some scenarios but not all. The SQL Server versions that support this functionality is SQL Server 2012 Enterprise, Evaluation and Developer Edition.

    Traditional Indexes are stored by rows instead of by columns. This kind of storage is extremely efficient when you require to access the data on a row or a small amount of rows. However if you request all the rows or a really big range, this approach becomes not as effective.

      

    The Store Column Indexes allow you to present a big range of rows by storing the data organizing it by columns. When you create the Column Store Index you usually include all the columns on a table, this will assure that all columns will

      

    In a Column Store Index, instead of storing all column of a specific row, each column is store a part from each other with all the rows from that column. The benefit of this type of index is that only the columns and rows needed to reply a request will be read. Usually a Dataware House scenarios only a 15% of the columns of an index to obtain the result of a request.

    There are two principal restriction to consider when working with Column Store Indexes. First a Column Store Index is read-only, once created you can modify the base table, this means operations like INSERT, UPDATE and DELETE are not allowed, for this reason is a good practice to use table partitioning to reduce that amount of data that need to be inserted on a Column Index Store and allow an easier reconstruction of an index when inserting new values to the table, because of this restriction the Column Index Store are more suited for scenarios that data don’t change frequently, like in Dataware House. The second restriction is that there can be only one Column Index Store per table, this limitation is not a real problem because you usually include ALL the columns of a table on the Column Index Store.

    Another limitation is related to the creation time of the Column Index Store in comparison with a non-clustered Index, the average time could be from 2 to 3 times longer than the non-clustered index. However, despite the restrictions mentioned earlier, the Column Index Store can provide a great value on performance benefits, and also the compression you get from similar data on the same page.

    The following data types can NOT be used on the Column Index Store: binary, varbinary, ntext, text, image, nvarchar(max), varchar(max), uniqueidentifier, rowversion, sql_variant, decimal (greater than 18 digits), datetimeoffset, xml, and data types based on CLR. Also the number of columns are restricted to 1024. Finally the index cannot be UNIQUE or CLUSTERED, contain Included Columns or have a defined order (Ascending or Descending)

    The Column Index Store use their own compression technology, and that why they can be combined with the page and row compression data options. They can’t also be used on replication schemas, change tracking, change data capture or filestream. This technologies works on a Read/Write scenario and that why are not compatible with the read-only nature of the Column Index Store.

    How to create a Column Index Store:

    The creation of the Column Index Store can be done through T-SQL or SQL Server Management Studio

    T-SQL

    CREATE NONCLUSTERED COLUMNSTORE INDEX <ColumnStoreIndexName> ON <Table> (col1, col2, col3);

    -- Create the columnstore index

    CREATE NONCLUSTERED COLUMNSTORE INDEX [csindx_FactResellerSalesPtnd]

    ON [FactResellerSalesPtnd]

    (

        [ProductKey], [OrderDateKey], [DueDateKey], [ShipDateKey], [CustomerKey], [EmployeeKey],

        [PromotionKey], [CurrencyKey], [SalesTerritoryKey], [SalesOrderNumber], [SalesOrderLineNumber],

        [RevisionNumber], [OrderQuantity], [UnitPrice], [ExtendedAmount], [UnitPriceDiscountPct],

        [DiscountAmount], [ProductStandardCost], [TotalProductCost], [SalesAmount], [TaxAmt], [Freight],

        [CarrierTrackingNumber], [CustomerPONumber], [OrderDate], [DueDate], [ShipDate]

    );

     

    Management Studio

    1. Through Managment Studio, use the Object Explorer to connect to the SQL Server Instance
    2. On the Object Explorer, expand the SQL Server Instance, the Database and the table where you want to create the Index.
    3.   Right Click on the mouse and select Non-Clustered Columnstore Index under the New Index option on Indexes.
    4. Give a name and select the columns involved on the Column Index Store. Press OK two times


    Column Index Store and Partitioned Tables

    Let’s create a partitioned table named FactResellerSalesPtnd using the following code from MSDN

    Step #1: Create the table FactResellerSalesPtnd (A Partittioned versión of: FactResellerSales)

    USE AdventureWorksDW2012;

    GO

     

    CREATE PARTITION FUNCTION [ByOrderDateMonthPF](int) AS RANGE RIGHT

    FOR VALUES (

        20050701, 20050801, 20050901, 20051001, 20051101, 20051201,

        20060101, 20060201, 20060301, 20060401, 20060501, 20060601,

        20060701, 20060801, 20060901, 20061001, 20061101, 20061201,

        20070101, 20070201, 20070301, 20070401, 20070501, 20070601,

        20070701, 20070801, 20070901, 20071001, 20071101, 20071201,

        20080101, 20080201, 20080301, 20080401, 20080501, 20080601,

        20080701, 20080801, 20080901, 20081001, 20081101, 20081201

    )

    GO

     

    CREATE PARTITION SCHEME [ByOrderDateMonthRange]

    AS PARTITION [ByOrderDateMonthPF]

    ALL TO ([PRIMARY])

    GO

     

    -- Create a partitioned version of the FactResellerSales table

    CREATE TABLE [dbo].[FactResellerSalesPtnd](

        [ProductKey] [int] NOT NULL,

        [OrderDateKey] [int] NOT NULL,

        [DueDateKey] [int] NOT NULL,

        [ShipDateKey] [int] NOT NULL,

        [CustomerKey] [int] NOT NULL,

        [EmployeeKey] [int] NOT NULL,

        [PromotionKey] [int] NOT NULL,

        [CurrencyKey] [int] NOT NULL,

        [SalesTerritoryKey] [int] NOT NULL,

        [SalesOrderNumber] [nvarchar](20) NOT NULL,

        [SalesOrderLineNumber] [tinyint] NOT NULL,

        [RevisionNumber] [tinyint] NULL,

        [OrderQuantity] [smallint] NULL,

        [UnitPrice] [money] NULL,

        [ExtendedAmount] [money] NULL,

        [UnitPriceDiscountPct] [float] NULL,

        [DiscountAmount] [float] NULL,

        [ProductStandardCost] [money] NULL,

        [TotalProductCost] [money] NULL,

        [SalesAmount] [money] NULL,

        [TaxAmt] [money] NULL,

        [Freight] [money] NULL,

        [CarrierTrackingNumber] [nvarchar](25) NULL,

        [CustomerPONumber] [nvarchar](25) NULL,

        OrderDate [datetime] NULL,

        DueDate [datetime] NULL,

        ShipDate [datetime] NULL

    ) ON ByOrderDateMonthRange(OrderDateKey);

    GO

     

    -- Using simple or bulk logged recovery mode, and then the TABLOCK

    -- hint on the target table of the INSERT…SELECT is a best practice

    -- because it causes minimal logging and is therefore much faster.

    ALTER DATABASE AdventureWorksDW2012 SET RECOVERY SIMPLE;

    GO

     

    -- Copy the data from the FactResellerSales into the new table

    INSERT INTO dbo.FactResellerSalesPtnd WITH(TABLOCK)

    SELECT * FROM dbo.FactResellerSales;

    GO

     

    -- Create the columnstore index

    CREATE NONCLUSTERED COLUMNSTORE INDEX [csindx_FactResellerSalesPtnd]

    ON [FactResellerSalesPtnd]

    (

        [ProductKey],

        [OrderDateKey],

        [DueDateKey],

        [ShipDateKey],

        [CustomerKey],

        [EmployeeKey],

        [PromotionKey],

        [CurrencyKey],

        [SalesTerritoryKey],

        [SalesOrderNumber],

        [SalesOrderLineNumber],

        [RevisionNumber],

        [OrderQuantity],

        [UnitPrice],

        [ExtendedAmount],

        [UnitPriceDiscountPct],

        [DiscountAmount],

        [ProductStandardCost],

        [TotalProductCost],

        [SalesAmount],

        [TaxAmt],

        [Freight],

        [CarrierTrackingNumber],

        [CustomerPONumber],

        [OrderDate],

        [DueDate],

        [ShipDate]

    );

     

    Step #2: Let’s execute an query and confirm if the Column Index Store was used

    1. a.       Press Ctrl+M, or select Include Actual Execution Plan from the Query menú on SQL Server Management Studio to actívate a graphical representation of the actual execution plan.
    2. b.      On the Query Editor execute the following query:

    SELECT SalesTerritoryKey, SUM(ExtendedAmount) AS SalesByTerritory

    FROM FactResellerSalesPtnd

    GROUP BY SalesTerritoryKey;


    Performance Benefits for Column Index Store

    Cost

    SELECT SalesTerritoryKey, SUM(ExtendedAmount) AS SalesByTerritory

    FROM FactResellerSales

    GROUP BY SalesTerritoryKey;

     

    -- Índice almacenado por columnas

    SELECT SalesTerritoryKey, SUM (ExtendedAmount) AS SalesByTerritory

    FROM FactResellerSalesPtnd

    GROUP BY SalesTerritoryKey;


    The relative cost of the Second Query (using a column index store) is 16% in comparison with the relative cost of the First Query (using a regular clústeres index)

    Disk I/O

     

    When executing the queries using STATISTICS IO ON (show the information related with the Disk I/O activity for the queries) you see a big improvement on the Second Query

     

    DBCC DROPCLEANBUFFERS

    DBCC FREEPROCCACHE

     

    SET STATISTICS IO ON

     

    SELECT SalesTerritoryKey, SUM(ExtendedAmount) AS SalesByTerritory

    FROM FactResellerSales

    GROUP BY SalesTerritoryKey;

     

    SELECT SalesTerritoryKey, SUM(ExtendedAmount) AS SalesByTerritory

    FROM FactResellerSalesPtnd

    GROUP BY SalesTerritoryKey;

     

    SET STATISTICS IO OFF

     

     

    (10 row(s) affected)

    Table 'FactResellerSales'. Scan count 1, logical reads 2982, physical reads 2, read-ahead reads 2972, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     

     

    (10 row(s) affected)

    Table 'FactResellerSalesPtnd'. Scan count 1, logical reads 599, physical reads 4, read-ahead reads 235, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     

    Time

    When executing the queries using STATISTICS IO ON (show the information in milliseconds related with the analyzing, compile and executing the queries) you see a time reduction on the Second Query

     

    DBCC DROPCLEANBUFFERS

    DBCC FREEPROCCACHE

     

    SET STATISTICS TIME ON

     

    SELECT SalesTerritoryKey, SUM(ExtendedAmount) AS SalesByTerritory

    FROM FactResellerSales

    GROUP BY SalesTerritoryKey;

     

    SELECT SalesTerritoryKey, SUM(ExtendedAmount) AS SalesByTerritory

    FROM FactResellerSalesPtnd

    GROUP BY SalesTerritoryKey;

     

    SET STATISTICS TIME OFF

     

    SQL Server Execution Times:

       CPU time = 46 ms, elapsed time = 109 ms.

     

    SQL Server Execution Times:

       CPU time = 32 ms, elapsed time = 85 ms.

     

    Conclusion

    Experts agree that the performance improvement when using column index store on a table fluctuate between 10% and 100%. However as mentioned in this article applications that benefit the most are the ones with high read volumes like data warehousing, but not for high write scenarios like OLTP systems. The star and snow flake schemas usually take part on data warehousing and datamarts where the velocity on data extraction is more important that the efficiency on the data manipulation. The column index store can improve this type of scenarios that are ideal for this technology.

     

     

     

  • COLUMN STORE INDEXES (ÍNDICES ALMACENADOS POR COLUMNAS)

     

    Hace unos días visite un cliente en Puerto Rico que tenía problemas de rendimiento en una aplicación transaccional y entre las opciones que quería considerar era el uso de índices almacenados por columnas. Este tipo de índice es una de las nuevas funcionalidades que tiene SQL 2012 pero como veremos a continuación sus características lo hacen idóneo para ciertos escenarios no para todos. Las versiones de SQL 2012 donde está disponible la funcionalidad de índices almacenados por columnas son: SQL Server 2012 Enterprise, Evaluation, y Developer.

     

    Los índices tradicionales son almacenados por filas en vez de columnas. Esta forma de almacenamiento es extremadamente eficiente cuando se requiere acceder una fila o un rango compuesto de un grupo pequeño de filas. Sin embargo cuando se solicitan todas las filas o un rango grande, este método se vuelve ineficiente.

      
     

    En un índice almacenado por columnas, en vez de almacenar juntas todas las columnas de un registro, cada columna es almacenada de forma separada con todas las demás filas en el índice. El beneficio de este tipo de índice consiste en que solo las columnas y las filas requeridas para contestar una consulta serán leídas. En escenarios de Datawarehouse, a menudo se utiliza menos de un 15% de las columnas de un índice para obtener el resultado de una consulta.

     

    Hay dos restricciones principales a considerar cuando se trabaja con índices almacenados por columnas. En primer lugar un índice almacenado por columna es de solo lectura. Una vez se ha creado, no se pueden realizar modificaciones a los datos en la tabla. Es decir las operaciones: INSERT, UPDATE y DELETE no están permitidas. Por esta razón a menudo se utiliza el particionamiento de tablas para reducir la cantidad de datos que necesitan ser almacenados en un índice almacenado por columnas y para permitir el reconstruir un índice cuando se insertan nuevos datos a la tabla. Debido a esta restricción, los índices almacenados por columnas son idóneos en situaciones donde los datos no cambian con frecuencia como es el caso de los datawarehouse. La segunda restricción limita a uno el número de índices almacenados por columnas que pueden existir en una tabla. Esta restricción no se considera un problema ya que se acostumbra a incluir todas las columnas de la tabla en el índice almacenado por columnas.

    Otra limitación está relacionada con el tiempo que toma la creación de un índice en comparación con un índice nonclustered. El tiempo promedio podría ser de dos a tres veces más. Sin embargo, a pesar de las restricciones antes mencionadas, los índices almacenados por columnas pueden proveer un valor significativo en términos de rendimiento si se considera que el índice solo cargará las columnas que sean requeridas por la consulta. Además de la mejora en compresión que se puede obtener por contar con data similar en la misma página.

    Los siguientes tipos de datos no pueden ser utilizados en índices almacenados por columnas: binary, varbinary, ntext, text, image, nvarchar(max), varchar(max), uniqueidentifier, rowversion, sql_variant, decimal (mayor de 18 dígitos), datetimeoffset, xml, y  tipos basados en CLR. Además el número de columnas está limitado a 1,024. Finalmente por la naturaleza del índice no puede ser UNIQUE, CLUSTERED, contener columnas incluidas o tener definido un orden (ascendente o descendente).

     

    Los índices almacenados por columnas utilizan su propia tecnología de compresión por lo cual no se pueden combinar con la opción de compresión a nivel de fila o página. Tampoco pueden ser utilizados en esquemas de replicación, change tracking o Change data capture, filestream. Estas tecnologías trabajan en escenarios de lectura/escritura lo cual no es compatible con la naturaleza de solo lectura de los índices almacenados por columnas.

    Como crear un índice almacenado por columnas

    La creación de un índice almacenado por columnas puede ser realizada a través de T-SQL o utilizando SQL Server Management Studio.

    T-SQL

    CREATE NONCLUSTERED COLUMNSTORE INDEX <ColumnStoreIndexName> ON <Table> (col1, col2, col3);

    -- Create the columnstore index

    CREATE NONCLUSTERED COLUMNSTORE INDEX [csindx_FactResellerSalesPtnd]

    ON [FactResellerSalesPtnd]

    (

        [ProductKey], [OrderDateKey], [DueDateKey], [ShipDateKey], [CustomerKey], [EmployeeKey],

        [PromotionKey], [CurrencyKey], [SalesTerritoryKey], [SalesOrderNumber], [SalesOrderLineNumber],

        [RevisionNumber], [OrderQuantity], [UnitPrice], [ExtendedAmount], [UnitPriceDiscountPct],

        [DiscountAmount], [ProductStandardCost], [TotalProductCost], [SalesAmount], [TaxAmt], [Freight],

        [CarrierTrackingNumber], [CustomerPONumber], [OrderDate], [DueDate], [ShipDate]

    );

     

    Management Studio

    1. A través del Management Studio, utilice el Object Explorer para conectarse a la instancia de SQL Server.
    2. En el Object Explorer, expanda la instancia de SQL Server, la base de datos y la tabla donde desea crear el índice.
    3. Presione el botón de la derecha del mouse y seleccione Non-Clustered Columnstore Index
      dentro de la opción New Index ubicada en la carpeta Indexes.
    4.  Ingrese el nombre y seleccione las columnas que participarán en el índice almacenado por columnas. Presione OK dos veces para crear el índice.


    Índices almacenados por columnas con tablas particionadas

    Crearemos una tabla particionada llamada FactResellerSalesPtnd utilizando el siguiente código de MSDN (http://msdn.microsoft.com/en-us/library/gg492088.aspx).

    Paso #1: Crear la tabla FactResellerSalesPtnd (Versión particionada de la tabla: FactResellerSales)

    USE AdventureWorksDW2012;

    GO

     

    CREATE PARTITION FUNCTION [ByOrderDateMonthPF](int) AS RANGE RIGHT

    FOR VALUES (

        20050701, 20050801, 20050901, 20051001, 20051101, 20051201,

        20060101, 20060201, 20060301, 20060401, 20060501, 20060601,

        20060701, 20060801, 20060901, 20061001, 20061101, 20061201,

        20070101, 20070201, 20070301, 20070401, 20070501, 20070601,

        20070701, 20070801, 20070901, 20071001, 20071101, 20071201,

        20080101, 20080201, 20080301, 20080401, 20080501, 20080601,

        20080701, 20080801, 20080901, 20081001, 20081101, 20081201

    )

    GO

     

    CREATE PARTITION SCHEME [ByOrderDateMonthRange]

    AS PARTITION [ByOrderDateMonthPF]

    ALL TO ([PRIMARY])

    GO

     

    -- Create a partitioned version of the FactResellerSales table

    CREATE TABLE [dbo].[FactResellerSalesPtnd](

        [ProductKey] [int] NOT NULL,

        [OrderDateKey] [int] NOT NULL,

        [DueDateKey] [int] NOT NULL,

        [ShipDateKey] [int] NOT NULL,

        [CustomerKey] [int] NOT NULL,

        [EmployeeKey] [int] NOT NULL,

        [PromotionKey] [int] NOT NULL,

        [CurrencyKey] [int] NOT NULL,

        [SalesTerritoryKey] [int] NOT NULL,

        [SalesOrderNumber] [nvarchar](20) NOT NULL,

        [SalesOrderLineNumber] [tinyint] NOT NULL,

        [RevisionNumber] [tinyint] NULL,

        [OrderQuantity] [smallint] NULL,

        [UnitPrice] [money] NULL,

        [ExtendedAmount] [money] NULL,

        [UnitPriceDiscountPct] [float] NULL,

        [DiscountAmount] [float] NULL,

        [ProductStandardCost] [money] NULL,

        [TotalProductCost] [money] NULL,

        [SalesAmount] [money] NULL,

        [TaxAmt] [money] NULL,

        [Freight] [money] NULL,

        [CarrierTrackingNumber] [nvarchar](25) NULL,

        [CustomerPONumber] [nvarchar](25) NULL,

        OrderDate [datetime] NULL,

        DueDate [datetime] NULL,

        ShipDate [datetime] NULL

    ) ON ByOrderDateMonthRange(OrderDateKey);

    GO

     

    -- Using simple or bulk logged recovery mode, and then the TABLOCK

    -- hint on the target table of the INSERT…SELECT is a best practice

    -- because it causes minimal logging and is therefore much faster.

    ALTER DATABASE AdventureWorksDW2012 SET RECOVERY SIMPLE;

    GO

     

    -- Copy the data from the FactResellerSales into the new table

    INSERT INTO dbo.FactResellerSalesPtnd WITH(TABLOCK)

    SELECT * FROM dbo.FactResellerSales;

    GO

     

    -- Create the columnstore index

    CREATE NONCLUSTERED COLUMNSTORE INDEX [csindx_FactResellerSalesPtnd]

    ON [FactResellerSalesPtnd]

    (

        [ProductKey],

        [OrderDateKey],

        [DueDateKey],

        [ShipDateKey],

        [CustomerKey],

        [EmployeeKey],

        [PromotionKey],

        [CurrencyKey],

        [SalesTerritoryKey],

        [SalesOrderNumber],

        [SalesOrderLineNumber],

        [RevisionNumber],

        [OrderQuantity],

        [UnitPrice],

        [ExtendedAmount],

        [UnitPriceDiscountPct],

        [DiscountAmount],

        [ProductStandardCost],

        [TotalProductCost],

        [SalesAmount],

        [TaxAmt],

        [Freight],

        [CarrierTrackingNumber],

        [CustomerPONumber],

        [OrderDate],

        [DueDate],

        [ShipDate]

    );

     

    Paso #2: Vamos a correr una consulta y confirmaremos que se utilizó el índice almacenado por columnas.

    1. a.       Presione Ctrl+M, o seleccione Include Actual Execution Plan desde el menú Query en SQL Server Management Studio para activar una representación gráfica del plan actual de ejecución.
    2. b.      En el Query Editor corra la siguiente consulta:

    SELECT SalesTerritoryKey, SUM(ExtendedAmount) AS SalesByTerritory

    FROM FactResellerSalesPtnd

    GROUP BY SalesTerritoryKey;


     

    Beneficios en términos de rendimiento de los índices almacenados por columnas

    Costo

    SELECT SalesTerritoryKey, SUM(ExtendedAmount) AS SalesByTerritory

    FROM FactResellerSales

    GROUP BY SalesTerritoryKey;

     

    -- Índice almacenado por columnas

    SELECT SalesTerritoryKey, SUM (ExtendedAmount) AS SalesByTerritory

    FROM FactResellerSalesPtnd

    GROUP BY SalesTerritoryKey;


    El costo relativo de la segunda consulta (utilizando un índice almacenado por columnas) es de 16% en comparación con el costo relativo de la primera consulta que es de 84% y utiliza un índice regular.

    Actividad de disco

    Al correr las consultas con STATISTICS IO ON (Muestra información relacionada con la cantidad de actividad de disco generada por las instrucciones Transact-SQL) se observa una mejora en rendimiento en la segunda consulta relacionada principalmente con las lecturas lógicas y read-ahead.

     

    DBCC DROPCLEANBUFFERS

    DBCC FREEPROCCACHE

     

    SET STATISTICS IO ON

     

    SELECT SalesTerritoryKey, SUM(ExtendedAmount) AS SalesByTerritory

    FROM FactResellerSales

    GROUP BY SalesTerritoryKey;

     

    SELECT SalesTerritoryKey, SUM(ExtendedAmount) AS SalesByTerritory

    FROM FactResellerSalesPtnd

    GROUP BY SalesTerritoryKey;

     

    SET STATISTICS IO OFF

     

     

    (10 row(s) affected)

    Table 'FactResellerSales'. Scan count 1, logical reads 2982, physical reads 2, read-ahead reads 2972, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     

     

    (10 row(s) affected)

    Table 'FactResellerSalesPtnd'. Scan count 1, logical reads 599, physical reads 4, read-ahead reads 235, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     

    Tiempo

    Al correr las consultas con STATISTICS TIME ON (Muestra el número de milisegundos necesarios para analizar, compilar y ejecutar cada instrucción) se observa una reducción de tiempo en la segunda consulta relacionada con el tiempo transcurrido y tiempo de CPU.

     

    DBCC DROPCLEANBUFFERS

    DBCC FREEPROCCACHE

     

    SET STATISTICS TIME ON

     

    SELECT SalesTerritoryKey, SUM(ExtendedAmount) AS SalesByTerritory

    FROM FactResellerSales

    GROUP BY SalesTerritoryKey;

     

    SELECT SalesTerritoryKey, SUM(ExtendedAmount) AS SalesByTerritory

    FROM FactResellerSalesPtnd

    GROUP BY SalesTerritoryKey;

     

    SET STATISTICS TIME OFF

     

    SQL Server Execution Times:

       CPU time = 46 ms, elapsed time = 109 ms.

     

    SQL Server Execution Times:

       CPU time = 32 ms, elapsed time = 85 ms.

     

    Conclusión

    Expertos coinciden que la mejora en rendimiento obtenida al utilizar índices almacenados en columnas fluctúa entre un 10% a un 100%. Sin embargo como hemos mencionado en este artículo las aplicaciones que se benefician más son las relacionadas con altos volúmenes de lectura y no en sistemas altamente transaccionales. Los esquemas de estrella y copo de nieve usualmente forman parte de los datawarehouse y datamarts donde la velocidad de la extracción de los datos es más importante que la eficiencia en la manipulación de los datos. La tecnología de índices almacenados en columnas puede detectar y agilizar las consultas dirigidas a estos esquemas por lo cual son los escenarios típicos idóneos para su aplicación.