De vez en cuando, como formador y consultor, tengo que justificar a nuestros clientes los beneficios que supone usar Integridad Referencial Declarativa (Claves Ajenas). Generalmente tengo comentarios del siguiente tipo:
hay muchos más casos aparte de estos cuatro; de mi experiencia, veo que la raiz del problema está relacionado con el ciclo de vida de sus aplicaciones (ALM), y mejorar sus buenas prácticas. Casi siempre, se puede llegar a una solución mejor diseñada y más segura; de hecho, durante las clases y reuniones, el cliente se queda convencido, pero días después, vuelven a la realidad de su trabajo y por diversas razones, es complicado cambiar los hábitos, y poner en marcha "lo que ya le has convencido" anteriormente.
Aquí pongo un post de Conor Cunningham (http://blogs.msdn.com/conor_cunningham_msft/archive/2008/08/07/foreign-keys-are-our-friends.aspx) donde muestra beneficios ocultos de usar DRI: Utilizando DRI el Optimizador de SQL Server, puede decidir no acceder a la tabla base, porque SQL Server ya sabe -- por la Integridad Referencial -- que si una fila existe en la tabla hija, tiene que existir en la tabla padre.
Además, me gustaría hacer algunas recomendaciones sobre todo esto, que quizás ves útil.
Usando un script muy similar al de Conor:
use AdventureWorks2008;go
drop table f1drop table t1go
create table t1(id int, v char(1) default 'a' , constraint t1_pk primary key (id))go
create table f1(f_id int identity, t_id int not null, v char(1) default 'a' , constraint f1_pk primary key (f_id) , constraint fk1 FOREIGN KEY (t_id) REFERENCES t1(id))
go
create nonclustered index nci_f1_t_id on f1 (t_id)
declare @i int=0set nocount onwhile @i < 2000begin insert into t1 (id) values (@i) insert into f1 (t_id) values (@i) insert into f1 (t_id) values (@i) insert into f1 (t_id) values (@i) set @i+= 1end
Connor comenta en su post, que las dos siguientes consultas no necesitan acceder a la tabla base -- puedes verlo en el plan de ejecución:
/* |--Index Scan(OBJECT:([AdventureWorks2008].[dbo].[f1].[nci_f1_t_id]))*/select f1.* from t1 inner join f1 on t1.id = f1.t_id
/* |--Index Scan(OBJECT:([AdventureWorks2008].[dbo].[f1].[nci_f1_t_id]))*/select f1.* from f1 where exists (select * from t1 where t1.id = f1.t_id)
Y me gustaría sugerir al equipo de SQL Server que considere también los dos siguientes casos:
/*|--Nested Loops(Inner Join) |--Clustered Index Seek(OBJECT:([AW].[dbo].[t1].[t1_pk]), SEEK:([AW].[dbo].[t1].[id]=(1)) ORDERED FORWARD) |--Nested Loops(Inner Join, OUTER REFERENCES:([AW].[dbo].[f1].[f_id])) |--Index Seek(OBJECT:([AW].[dbo].[f1].[nci_f1_t_id]), SEEK:([AW].[dbo].[f1].[t_id]=(1)) ORDERED FORWARD) |--Clustered Index Seek(OBJECT:([AW].[dbo].[f1].[f1_pk]), SEEK:([AW].[dbo].[f1].[f_id]=[AW].[dbo].[f1].[f_id]) LOOKUP ORDERED FORWARD)
*/select f1.* from t1 inner join f1 on t1.id = f1.t_idwhere f1.t_id = 1
/* |--Merge Join(Inner Join, MERGE:([AW].[dbo].[t1].[id])=([AW].[dbo].[f1].[t_id]), RESIDUAL:([AW].[dbo].[f1].[t_id]=[AW].[dbo].[t1].[id])) |--Clustered Index Scan(OBJECT:([AW].[dbo].[t1].[t1_pk]), ORDERED FORWARD) |--Index Scan(OBJECT:([AW].[dbo].[f1].[nci_f1_t_id]), ORDERED FORWARD)*/select t1.id, f1.* from t1 inner join f1 on t1.id = f1.t_id
La primera consulta, implementa una consulta tipica OLTP en la que busca todas las filas hija de una clave primaria concreta de la tabla padre. En este ejemplo, SQL Server ya sabes que para cada fila de la tabla hija (f1), por lo menos existe una fila en la tabal padre (t1). La única diferencia con la consulta del post de Connor es que la consulta tiene un predicado WHERE, que yo creo que es lo que hace que se comporte de forma diferente. Yo creo que como el filtro es muy selectivo, y es más selectivo en la tabla padre (1 padre por cada 4 hijos), entonces "coloca" la tabla t1 como muy candidata a ser accedida.
La segunda consulta, me resulta un poco sorprendente, porque SQL Server ya sabe que t1.id = f1.t_id. ¿Por qué acceder a la tabal t1, si ya sabe que el valor está también en f1.t_id?
Si crees que esta dos "sugerencias lógicas" debería considerarlas SQL Server, haz login en Connect, y vota por la sugerencia (https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=363429) :).
Más de una vez me lo han preguntado y he aquí una solución:
http://ssis.trigonblue.com/Excelin64bitenv/tabid/523/Default.aspx
Otra solución puede ser montar un SQL Express/Instancia SQL Server 32 bits y usar los drivers 32 bits.
Por cierto, también está disponible, el driver OLEDB para ODBC en 64 bits, para Windows 2003 -- no se si sirve también para Windows 2008:
http://blogs.msdn.com/data/archive/2008/04/07/64-bit-oledb-provider-for-odbc-msdasql-is-now-available-for-windows-server-2003.aspx
Es bien conocido por todos que nos arriesgamos a graves problemas de rendimiento en SQL Server si no mantenemos las estadísticas razonablemente actualizadas. Las estadísticas e histogramas son una fuente de información vital para que el optimizador basado en coste realice su labor de forma adecuada. El uso de un índice, de un método de acceso u otro a una tabla quedan determinados, por los datos estadísticos de los que se disponga.
Una vez que conocemos lo importante que es mantener dichas estadísticas actualizadas, debemos determinar la mejor estrategia para conseguirlo minimizando el impacto en nuestro sistema. No olvidemos que actualizar estadísticas, especialmente en tablas grandes, es una tarea que tiene un consumo de recursos considerable. En algunos escenarios es suficiente con delegar esta actualización de las estadísticas a SQL Server (habilitando la auto creación y actualización de estadísticas) y en otros podemos preferir hacerlo de forma manual para tener un mayor control. En la mayoría de los casos el permitir a SQL Server realizar este mantenimiento automáticamente será más que suficiente pero no por ello podemos confiar ciegamente en ello. Es por ello que habitualmente recomendamos, adicionalmente, incluir en nuestro plan de mantenimiento un proceso de actualización de estadísticas.
Con SQL Server 2005 tenemos una nueva funcionalidad de actualización de estadísticas asíncrona. ¿Qué nos aporta la actualización asíncrona? Básicamente una mejor estabilidad en el tiempo de respuesta ante consultas de nuestro servidor. Para comprobar que estamos en lo cierto, nada mejor que un ejemplo J
Partiendo de un escenario con actualización síncrona y automática de estadísticas (configuración por defecto en SQL Server 2005) vamos a analizar el tiempo de respuesta de una misma consulta ejecutada N veces mientras se producen actualizaciones de estadísticas. Para conseguir que se actualicen las estadísticas, provocaremos algunos cambios significativos (que acumulen >20% filas) sobre una tabla mientras realizamos consultas sobre dichos datos.
-- Creamos una copia de order details vacía
select * into orderscopy from northwind.dbo.[Order Details]
where 1=2;
-- Añadimos un índice
create index iOrdersCopy on orderscopy(OrderId,ProductID);
-- Añadimos filas y vamos consultando en un bucle
while (1=1)
begin
insert into orderscopy select * from northwind.dbo.[Order Details]
select * from orderscopy where orderid=10253 and ProductID > 39
end
El script se limita a, dentro de un bucle infinito, insertar filas y realizar una consulta bastante selectiva sobre el conjunto de éstas. A continuación ejecutaremos, con la configuración por defecto al crear una base de datos, el script y mediremos los tiempos de ejecución (en us) de las inserciones y las consultas.
A continuación modificaremos la base de datos para que utilice actualizaciones asíncronas de estadísticas y vaciaremos la tabla orderscopy antes de lanzar el script de nuevo. Para cambiar a modo asíncrono ejecutaremos el siguiente ALTER:
alter database stats
set AUTO_UPDATE_STATISTICS_ASYNC ON
Y estos son los resultados en modo asíncrono:
Parece ser que no estábamos tan en lo cierto (o eso parece vistos estos resultados). Como podemos ver, no existen grandes diferencias entre ambas alternativas (síncrona vs asíncrona) y en ambas se obtiene un patrón similar. Esto es lógico pues depende del porcentaje de cambios que provocan la actualización de estadísticas y esto solo depende del número de ejecuciones del bucle. Algo se nos está escapando por lo que, traza de profiler en mano, procedemos a analizar una actualización síncrona y una asíncrona:
Actualización síncrona
Como podemos ver, la ejecución del SELECT paga el precio de la actualización síncrona de las estadísticas. Un batch que únicamente necesita unos escasos ms para la consulta acaba necesitando más de medio segundo para su ejecución. Además, siempre tendremos la duda de cuanta mejora aporta realmente el plan "más optimizado" que generará el optimizador gracias a dichas estadísticas actualizadas. En resumen, estamos pagando un alto precio de forma síncronamente a la consulta.
Actualización asíncrona
En el caso de la actualización asíncrona vemos que la ejecución de la SELECT únicamente "anota" la operación pendiente de realizar. Esto hace que el batch completo (anotación + SELECT) no llegue a los 20ms. Mientras tanto, de forma asíncrona, se realiza la actualización de estadísticas la cual acaba necesitando casi de 2 segundos. Debemos tener en cuenta que en este caso la tabla de la que tenemos que generar estadísticas es relativamente pequeña (3768 KB).
¿Qué alternativa es la mejor? La respuesta, como casi siempre, es "DEPENDE" J
La opción por defecto (actualizaciones síncronas) nos permite tener una mayor garantía de que no tendremos planes de ejecución "no optimos" debido a unas estadísticas que no están actualizadas a cambio de arriesgarnos a pagar un alto precio si actualizar las estadísticas lleva más tiempo del previsto. Un ejemplo típico es un sistema OLTP donde la duración de las transacciones es crítica y debe ser lo más corta posible. En estos sistemas habitualmente se realizan muchas transacciones de forma simultánea, afectando a un número reducido de datos pero sobre un conjunto de éstos bastante grande. En este escenario no suelen ser habituales las actualizaciones de estadísticas pero podrían darse si otros procesos externos realizan operaciones con cambios masivos.
Las actualizaciones asíncronas nos libran de la atadura de tener que actualizar las estadísticas "en ese momento" a cambio de poder tener planes subóptimos en algunas ocasiones. En sistemas donde se realicen modificaciones importantes sobre tablas e, inmediatamente, se lancen consultas sobre dichas tablas este comportamiento puede ser muy indeseable. Esto nos lleva a pensar que probablemente no sería recomendable tener actualizaciones asíncronas en sistemas con predominio de procesos batch, importaciones de datos y recálculos masivos, con mucho uso de tablas temporales de un solo uso, etc.
En conclusión creo que en aquellos escenarios donde no tenemos un control total sobre las operaciones que se realizan en la base de datos y no podemos hilar siempre muy fino es preferible tener actualizaciones síncronas. No obtendremos la mejor de las latencias y posiblemente suframos de algunos "parones" en algunas consultas pero minimizamos el riesgo que unas pocas consultas OLTP "optimizadas" en base a unas estadísticas no adecuadas nos den un gran disgusto. El uso de actualizaciones asíncronas lo reservaríamos para aquellas bases de datos donde tengamos necesidades de baja latencia aunque tengamos que "trabajarnos" un poco más los procesos batch/masivos. Si tenemos el control total de las operaciones (por ejemplo con una API de procedimientos almacenados), nada nos impide incluir los "sp_updatestats/update statistics" que consideremos apropiados antes de cualquier consulta que, tras analizarla, es demasiado arriesgado ejecutarla sin unas buenas estadísticas. Seguramente más de uno pueda pensar que esto sería "matar moscas a cañonazos" pero no debemos olvidar que la actualización de estadísticas en SQL Server 2005 es suficientemente inteligente como para no llevar a cabo la actualización de estadísticas si los datos no han cambiado lo suficiente J
Como en otras ocasiones, ponemos disponible para descarga en formato electrónico la documentación que se utilizó para realizar las presentaciones del SQL Server 2008 Jumpstart que en esta ocasión tuvo lugar en las oficinas de Barcelona que posee Microsoft.
Tanto documentación como ejemplos están preparados para la beta RC0 que apareció recientemente de SQL Server 2008, así que atento por si intentas utilizarlos sobre versiones beta anteriores o la próxima versión final del producto cuando sea liberada.
Aquí os dejo los materiales: Slides relacional Jumpstart RC0
Y aquí las demos: DEMOS Jumpstart 2008 Relacional
Recordad que podéis acceder a los Webcasts que grabamos para Microsoft, así como acceder a la grabación de todas las demos que hicimos para el Jumpstart en Marzo, descargable desde aquí: Demostraciones de features de SQL Server 2008 en video (.WMV)
Ayer día 8 de Julio y como segundo martes de cada mes, Microsoft publicó una serie de actualizaciones para sus productos. Entre ellos se encontraba una actualización de seguridad de un producto que pocas veces estamos acostumbrados a encontrar, SQL Server.
Dicha actualización se aplica no solo a SQL Server 2005 SP2, sino incluso a SQL Server 7 SP4 y SQL Server 2000 SP4. Corrige errores calificados como "importantes" dado que permiten la elevación de privilegios en función de la edición de SQL Server y el tipo de ataque entre estos:
En el caso de SQL Server 2005, la actualización nos dejará el motor en el nº de versión 9.0.3068
El boletín oficial donde podéis encontrar la descripción técnica lo podéis leer aquí.
Salvador Ramos para la parte de BI y un servidor nos vamos a encargar de impartir las sesiones del próximo SQL Server 2008 Jumpstart en Barcelona los próximos dias 14 y 15 de Julio.
Durante dos dias, repasaremos las novedades y mejoras que aparecen en SQL Server 2008 tanto en el apartado relacional, como en el de inteligencia de negocio.
CREATE TABLE [dbo].[Roles](
[RoleID] [uniqueidentifier] NOT NULL,
[RoleName] [nvarchar](260) NOT NULL,
[Description] [nvarchar](512) NULL,
[TaskMask] [nvarchar](32) NOT NULL,
[RoleFlags] [tinyint] NOT NULL,
CONSTRAINT [PK_Roles] PRIMARY KEY NONCLUSTERED
(
[RoleID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE UNIQUE CLUSTERED INDEX [IX_Roles] ON [dbo].[Roles]
[RoleName] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'114e91eb-1868-4cf7-8e9d-f43a19813b7f', N'Browser', N'May view folders, reports and subscribe to reports.', N'0010101001000100', 0)
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'9bc89cad-3d9a-491d-be02-75be1579af2c', N'Content Manager', N'May manage content in the Report Server. This includes folders, reports and resources.', N'1111111111111111', 0)
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'0286b7fb-a18b-4fa4-bb59-adb7cf28df73', N'Model Item Browser', N'Allows users to view model items in a particular model.', N'1', 2)
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'29c8f388-b3f6-4a4d-a7d9-eed72f3c0bd2', N'My Reports', N'May publish reports and linked reports; manage folders, reports and resources in a users My Reports folder.', N'0111111111011000', 0)
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'5d9fa665-e315-4249-b519-0e3668f3d49d', N'Publisher', N'May publish reports and linked reports to the Report Server.', N'0101010100001010', 0)
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'e51e9553-4f47-4619-a943-05020a373862', N'Report Builder', N'May view report definitions.', N'0010101001000101', 0)
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'8ca532d7-7e5c-4f18-8976-d2d4ffd95472', N'System Administrator', N'View and modify system role assignments, system role definitions, system properties, and shared schedules.', N'110101011', 1)
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'a5f16ea9-fb16-4898-b8f1-9d05351f56de', N'System User', N'View system properties and shared schedules.', N'001010001', 1)
Podemos elegir al generar el script la versión de SQL Server (2000, 2005 o 2008) Como pequeño tirón de orejas , indicar que el ejemplo anterior ha sido generado para "SQL Server 2008" y vemos que no se están utilizando los nuevos constructores de fila para generar los insert.
Microsoft presentó durante el Microsoft Hosting Summit 2008 una nueva versión de SQL Server 2008 específica para las necesidades de las empresas de hosting. El objetivo es luchar contra la mayoritaria oferta de hosting Apache+PHP+MySQL y ganar cuota de mercado en IIS+ASP.NET+SQL Server.
Por ahora se cuenta con pocos detalles (de hecho no aparece como versión en la web oficial de SQL Server 2008) pero se especula que pueda ofrecer funcionalidades interesantes de Enterprise a un bajo precio por licencia :) Por ejemplo algunas de estas funcionalidades no confirmadas serían Resource Governor y Backup compression.
Como seguro que sabreis, Solid Quality Mentors esta siendo la encargada de realizar los webcasts de la nueva versión de la plataforma de datos de microsoft SQL Server 2008. Pues bien, ya estan colgados los tres primeros webcasts para su visionado a traves de Windows Media Player.
Aquí os dejo los enlaces:
Que los disfruteis.