Un retraso en el envío de subscripciones podría ser debido a que las theads del procesamiento del servicio de Reporting Services no estén procesando los eventos a tiempo, quizás porque estén sobrecargadas y no les de tiempo a ser ejecutadas tan pronto como llegan. En este caso, los Jobs se ejecutarían en el SQL Agent pero las tablas “Events” y “Notifications” mostrarían un numero muy alto de entradas. Como el número de threads es fijo, no se puede incrementar, entonces, convendría averiguar por qué no se están procesando a tiempo, o acumulando.
El email de mi subscripción no llega al destinatario
http://blogs.msdn.com/b/esecuelesinfronteras/archive/2009/10/19/el-email-de-mi-subscripci-n-no-llega-al-destinatario.aspx
Como ves en el siguiente blog, hay un par de consultas que se pueden utilizar para obtener la información sobre las subscriciones de las tablas de la base de datos de ReportServer, y así poder hacer un mejor diagnostico:
1. Primera consulta:
select 'SubnDesc'= s.Description, 'SubnOwner= us.UserName, 'LastStatus'= s.LastStatus, 'LastRun'= s.LastRunTime, 'ReportPath'= c.Path, 'ReportModifiedBy'= uc.UserName, 'ScheduleId'= rs.ScheduleId, 'SubscriptionId'= s.SubscriptionID from ReportServer.dbo.Subscriptions s join ReportServer.dbo.Catalog c on c.ItemID = s.Report_OID join ReportServer.dbo.ReportSchedule rs on rs.SubscriptionID = s.SubscriptionID join ReportServer.dbo.Users uc on uc.UserID = c.ModifiedByID join ReportServer.dbo.Users us on us.UserID = s.OwnerId Join msdb.dbo.sysjobs j on j.name = CONVERT(nvarchar(128),rs.ScheduleId)
select
'SubnDesc'= s.Description,
'SubnOwner= us.UserName,
'LastStatus'= s.LastStatus,
'LastRun'= s.LastRunTime,
'ReportPath'= c.Path,
'ReportModifiedBy'= uc.UserName,
'ScheduleId'= rs.ScheduleId,
'SubscriptionId'= s.SubscriptionID
from
ReportServer.dbo.Subscriptions s
join
ReportServer.dbo.Catalog c on c.ItemID = s.Report_OID
ReportServer.dbo.ReportSchedule rs on rs.SubscriptionID = s.SubscriptionID
ReportServer.dbo.Users uc on uc.UserID = c.ModifiedByID
ReportServer.dbo.Users us on us.UserID = s.OwnerId
Join
msdb.dbo.sysjobs j on j.name = CONVERT(nvarchar(128),rs.ScheduleId)
2. Segunda consulta:
select 'Report'= c.Path, 'Subscription'= s.Description, 'SubscriptionOwner'= uo.UserName, 'SubscriptionModBy'= um.UserName, 'SubscriptionModDate'= s.ModifiedDate, 'ProcessStart'= dateadd(hh,DATEDIFF(hh,Getutcdate(),Getdate()),n.ProcessStart), 'NotificationEntered'= dateadd(hh,DATEDIFF(hh,Getutcdate(),Getdate()),n.NotificationEntered), 'ProcessAfter'= dateadd(hh,DATEDIFF(hh,Getutcdate(),Getdate()),n.ProcessAfter), n.Attempt, 'SubscriptionLastRunTime'= dateadd(hh,DATEDIFF(hh,Getutcdate(),Getdate()),n.SubscriptionLastRunTime), n.IsDataDriven, 'ProcessHeartbeat'= dateadd(hh,DATEDIFF(hh,Getutcdate(),Getdate()),n.ProcessHeartbeat), n.Version, n.SubscriptionID from Notifications n join Subscriptions s on n.SubscriptionID = s.SubscriptionID join Catalog c on c.ItemID = n.ReportID join Users uo on uo.UserID = s.OwnerID join Users um on um.UserID = s.ModifiedByID
'Report'= c.Path,
'Subscription'= s.Description,
'SubscriptionOwner'= uo.UserName,
'SubscriptionModBy'= um.UserName,
'SubscriptionModDate'= s.ModifiedDate,
'ProcessStart'= dateadd(hh,DATEDIFF(hh,Getutcdate(),Getdate()),n.ProcessStart),
'NotificationEntered'= dateadd(hh,DATEDIFF(hh,Getutcdate(),Getdate()),n.NotificationEntered),
'ProcessAfter'= dateadd(hh,DATEDIFF(hh,Getutcdate(),Getdate()),n.ProcessAfter),
n.Attempt,
'SubscriptionLastRunTime'= dateadd(hh,DATEDIFF(hh,Getutcdate(),Getdate()),n.SubscriptionLastRunTime),
n.IsDataDriven,
'ProcessHeartbeat'= dateadd(hh,DATEDIFF(hh,Getutcdate(),Getdate()),n.ProcessHeartbeat),
n.Version,
n.SubscriptionID
Notifications n
Subscriptions s on n.SubscriptionID = s.SubscriptionID
Catalog c on c.ItemID = n.ReportID
Users uo on uo.UserID = s.OwnerID
Users um on um.UserID = s.ModifiedByID
Más información en:
Dean Kalanquin's Reporting Services Blog http://blogs.msdn.com/b/deanka/
Dean Kalanquin's Reporting Services Blog
http://blogs.msdn.com/b/deanka/
Maria Esteban
Reporting Services Support Engineer
En la anterior entrada hicimos una breve comparación de rendimiento de los diferentes proveedores de acceso bajo código manejado, esta vez vamos a enfrentarlos en el mundo nativo y ver cual sale vencedor.
Como en el articulo anterior empiezo pegando el código usado para la prueba, es el siguiente:
printf("\nNative code and OLEDB\n"); //OLEDB HRESULT hr; IDBInitialize* pIDBInitialize = NULL; IDBProperties* pIDBProperties = NULL; IDBCreateSession* pIDBCreateSession = NULL; IDBCreateCommand* pIDBCreateCommand = NULL; ICommandText* pICommandText = NULL; ICommandProperties* pICommandProperties = NULL;
DBPROPSET dbPropSet; DBPROP dbProp[5];
//Fire up COM hr = CoInitialize(0);
for (int i = 0; i<10; i++) { //Create provider instance hr = CoCreateInstance(CLSID_SQLNCLI10, NULL, CLSCTX_INPROC_SERVER, IID_IDBProperties, (void **) &pIDBProperties);
//Setup properites dbProp[0].dwPropertyID = DBPROP_INIT_DATASOURCE; dbProp[0].dwOptions = DBPROPOPTIONS_REQUIRED; dbProp[0].colid = DB_NULLID; V_VT(&(dbProp[0].vValue)) = VT_BSTR; V_BSTR(&(dbProp[0].vValue)) = SysAllocString(L"PGAVELA-02\\KAWORU");
dbProp[1].dwPropertyID = DBPROP_AUTH_INTEGRATED; dbProp[1].dwOptions = DBPROPOPTIONS_REQUIRED; dbProp[1].colid = DB_NULLID; V_VT(&(dbProp[1].vValue)) = VT_BSTR; V_BSTR(&(dbProp[1].vValue)) = SysAllocString( L"SSPI" );
dbProp[2].dwPropertyID = DBPROP_INIT_CATALOG; dbProp[2].dwOptions = DBPROPOPTIONS_REQUIRED; dbProp[2].colid = DB_NULLID; V_VT(&(dbProp[2].vValue)) = VT_BSTR; V_BSTR(&(dbProp[2].vValue)) = SysAllocString( L"PerfTest" );
dbPropSet.rgProperties = dbProp; dbPropSet.cProperties = 3; dbPropSet.guidPropertySet = DBPROPSET_DBINIT;
hr = pIDBProperties->SetProperties(1, &dbPropSet);
SysFreeString( V_BSTR(&(dbProp[0].vValue)) ); SysFreeString( V_BSTR(&(dbProp[1].vValue)) ); SysFreeString( V_BSTR(&(dbProp[2].vValue)) );
//Create the Session and Command hr = pIDBProperties->QueryInterface( IID_IDBInitialize, (void**) &pIDBInitialize);
hr = pIDBInitialize->Initialize();
hr = pIDBInitialize->QueryInterface( IID_IDBCreateSession, (void**) &pIDBCreateSession );
hr = pIDBCreateSession->CreateSession(NULL, IID_IDBCreateCommand, (IUnknown **) &pIDBCreateCommand);
hr = pIDBCreateCommand->CreateCommand(NULL, IID_ICommandText, (IUnknown **) &pICommandText);
//Setup Command Properties pICommandText->QueryInterface( IID_ICommandProperties, (void **) &pICommandProperties );
dbPropSet.rgProperties = dbProp; dbPropSet.cProperties = 5; dbPropSet.guidPropertySet = DBPROPSET_ROWSET;
hr = pICommandProperties->SetProperties( 1, &dbPropSet);
pICommandProperties->Release(); pICommandProperties=NULL;
IRowset* pIRowset = NULL; IAccessor* pIAccessor = NULL;
wchar_t* pData = NULL; DBCOUNTITEM cRowsObtained = 0; ULONG cCount = 0;
HROW* pRows = new HROW[1000000]; HACCESSOR hAccessor = 0; DBBINDING Bind[1]; hr = pICommandText->SetCommandText(DBGUID_SQL, L"SELECT TOP 5000000 * FROM HashTable"); startTicks = GetTickCount(); hr = pICommandText->Execute(NULL, IID_IRowset, NULL, NULL, (IUnknown**)&pIRowset); //Bind data Bind[0].dwPart = DBPART_VALUE; Bind[0].eParamIO = DBPARAMIO_NOTPARAM; Bind[0].iOrdinal = 1; Bind[0].pTypeInfo = NULL; Bind[0].pObject = NULL; Bind[0].pBindExt = NULL; Bind[0].dwFlags = 0; Bind[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED; Bind[0].obLength = 0; Bind[0].obStatus = 0; Bind[0].obValue = 0; Bind[0].cbMaxLen = 500; Bind[0].wType = DBTYPE_WSTR; Bind[0].bPrecision = 0; Bind[0].bScale = 0;
hr = pIRowset->QueryInterface(IID_IAccessor, (void**)&pIAccessor);
hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 1, Bind, 0, &hAccessor, NULL);
pData = new wchar_t[500];
do { //Get Next 10 rows hr = pIRowset->GetNextRows(NULL,0,10,&cRowsObtained, &pRows);
for (int cCount =0; cCount < cRowsObtained; cCount++) { hr = pIRowset->GetData(pRows[cCount], hAccessor, pData); } pIRowset->ReleaseRows(cRowsObtained,pRows,NULL,NULL,NULL);
}while(cRowsObtained != 0); endTicks = GetTickCount(); totalTicks = endTicks - startTicks; printf("Round %d Time: %d ms\n",i,totalTicks); totalOLEDB += totalTicks; //Release all objects pIAccessor->Release(); pIRowset->Release(); pICommandText->Release(); pIDBCreateCommand->Release(); pIDBCreateSession->Release(); pIDBInitialize->Release(); pIDBProperties->Release(); } printf("Avg Time: %d ms\n",totalOLEDB/10);
Como podemos ver hacemos lo mismo que con el código manejado, diez iteraciones y luego obtenemos la media, los resultados son los siguientes:
Native code and OLEDB Round 0 Time: 23743 ms Round 1 Time: 24165 ms Round 2 Time: 16068 ms Round 3 Time: 23385 ms Round 4 Time: 22870 ms Round 5 Time: 24445 ms Round 6 Time: 24383 ms Round 7 Time: 26395 ms Round 8 Time: 23572 ms Round 9 Time: 23822 ms Avg Time: 23284 ms
Como podemos ver el tiempo medio es de 23,3 segundos una mejora importante respecto a la version de .NET pero aún así más lento que SQLClient.
Y por último tenemos ODBC, a continuación pego el código utilizado:
SQLRETURN ret; SQLHDBC dbc; SQLHENV env; SQLHSTMT stmt; DWORD startTicks; DWORD endTicks; DWORD totalTicks; SQLWCHAR texto[500]; wchar_t resultado[500];
DWORD totalODBC = 0; DWORD totalOLEDB = 0;
SQLINTEGER cursorOptions = SQL_CO_FFO; printf("Native code and ODBC\n"); for (int i=0; i<10;i++) { SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&env); SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0); SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc); ret = SQLDriverConnect(dbc, NULL, L"Driver={SQL Server Native Client 10.0};Server=PGAVELA-02\\KAWORU;Database=PerfTest;Trusted_Connection=yes", SQL_NTS,NULL,0,NULL,SQL_DRIVER_COMPLETE); if (SQL_SUCCEEDED(ret)) { SQLAllocHandle(SQL_HANDLE_STMT, dbc,&stmt); SQLPrepare(stmt, L"SELECT TOP 5000000 * FROM HashTable", SQL_NTS); SQLSetStmtAttr(stmt, SQL_SOPT_SS_CURSOR_OPTIONS,&cursorOptions,NULL); SQLBindCol(stmt, 1, SQL_C_WCHAR, texto, 500, NULL); startTicks = GetTickCount(); SQLExecute(stmt); while (SQL_SUCCEEDED(ret = SQLFetch(stmt))) { wsprintf(resultado,texto); } endTicks = GetTickCount(); totalTicks = endTicks - startTicks; printf("Round %d Time: %d ms\n",i,totalTicks); totalODBC += totalTicks; SQLDisconnect(dbc); SQLFreeHandle(SQL_HANDLE_STMT, stmt); SQLFreeHandle(SQL_HANDLE_DBC, dbc); SQLFreeHandle(SQL_HANDLE_ENV, env); } }
printf("Avg Time: %d ms\n",totalODBC / 10);
A continuación los tiempos:
Native code and ODBC Round 0 Time: 10686 ms Round 1 Time: 10452 ms Round 2 Time: 10343 ms Round 3 Time: 10436 ms Round 4 Time: 8939 ms Round 5 Time: 11107 ms Round 6 Time: 10327 ms Round 7 Time: 9718 ms Round 8 Time: 10406 ms Round 9 Time: 10686 ms Avg Time: 10310 ms
Wow! 10 segundos, bastante más rápido que SQLClient. Por tanto en nativo no hay color, ¡ODBC es el camino a seguir!
Como podemos ver OleDB es el proveedor más lento de todos accediendo a SQL Server, además teniendo en cuenta que se considera deprecado no se recomienda usarlo, en aquellos desarrollos que ya tengamos os recomendamos cambiar a ODBC o SQLClient(Solo en manejado) lo antes posible.
Con esta recomendación podéis ver que en nativo ODBC es el camino a seguir sin ninguna duda, pero en manejado, usamos SQLClient u ODBC. Pues depende de lo que busquemos, ODBC al tener que traducir de nativo a manejado y viceversa sufre cierta penalización cuando lo usamos bajo código manejado, por tanto si lo que queremos es rendimiento la respuesta es sencilla, SQLClient.
Si nos interesa la portabilidad ya que en un futuro puede que cambiemos de SQL Server a otra base de datos, entonces la respuesta no es tan sencilla. Si tenemos proveedores manejados para otras bases de datos que se ajusten al modelo de acceso de System.Data entonces puede que necesitemos cambiar no muchas lineas de codigo. Pero si usamos ODBC el cambio puede ser muchisimo más sencillo (si no usamos comandos SQL “extraños”) bastaría con cambiar la cadena de conexión y en principio,si hay suerte, puede que nada más.
Pablo Gavela López – Microsoft Customer Support Services
En ocasiones nos podemos preguntar que proveedor usar al desarrollar nuestra capa de acceso a datos, usamos OLEDB, ODBC o SQLClient. que ventajas tienen unos, cuales otros… En estos post intentaré ver la diferencia de rendimiento entre los diferentes proveedores y que ventajas podemos tener si usamos unos u otros.
Para esta prueba vamos a utilizar una tabla de una sola columna (nvarchar(max) y varios millones de columnas. La query que vamos a ejecutar es la siguiente:
SELECT TOP 5000000 * FROM HashTable
En este primer articulo vamos a centrarnos en código manejado y las diferencias de rendimiento entre usar SQLClient, Odbc, Oledb. Todas las pruebas se realizan en local y conectando con TCP.
Para empezar veremos la que probablemente sea forma más común de acceder a SQL Server desde .NET, SQLClient. El código que vamos a ejecutar es el siguiente:
for (int j = 0; j < 10; j++) { SqlConnection conn = new SqlConnection("Data Source=PGAVELA-02\\KAWORU;Initial Catalog=PerfTest;Integrated Security=SSPI"); SqlCommand cmd = new SqlCommand("SELECT TOP 5000000 * FROM HashTable"); cmd.Connection = conn; cmd.CommandTimeout = 0; conn.Open(); startTick = Environment.TickCount; cmd.Prepare(); SqlDataReader reader = cmd.ExecuteReader(); while (reader.Read()) { reader.GetString(0); } endTick = Environment.TickCount; Console.WriteLine(String.Format("Round {0} Time: {1} ms", j, endTick - startTick)); SQLClient += (endTick - startTick); conn.Close(); }
Console.WriteLine(String.Format("Avg Time: {0} ms", SQLClient / 10.0));
Como podemos ver ejecutamos 10 veces la query y al final obtenemos el tiempo medio.
Managed Code And SQLClient Round 0 Time: 15803 ms Round 1 Time: 15709 ms Round 2 Time: 15694 ms Round 3 Time: 15802 ms Round 4 Time: 15757 ms Round 5 Time: 15818 ms Round 6 Time: 15585 ms Round 7 Time: 15568 ms Round 8 Time: 15601 ms Round 9 Time: 15678 ms Avg Time: 15701,5 ms
Como podemos ver el el tiempo medio es de 15.7 segundos.
La segunda prueba la vamos a realizar con OleDB, el código que vamos a ejecutar es practicamente el mismo que con SQLClient, pero cambiando a las clases apropiadas de OleDB.
Console.WriteLine("Managed Code And OLEDB"); for (int j = 0; j < 10; j++) { OleDbConnection OleDbConn = new OleDbConnection("Provider=SQLNCLI10.1;Integrated Security=SSPI;Initial Catalog=PerfTest;Data Source=PGAVELA-02\\KAWORU"); OleDbCommand OleDbCmd = new OleDbCommand("SELECT TOP 5000000 * FROM HashTable"); OleDbCmd.Connection = OleDbConn; OleDbCmd.CommandTimeout = 0; OleDbConn.Open(); startTick = Environment.TickCount; OleDbCmd.Prepare(); OleDbDataReader OleDbReader = OleDbCmd.ExecuteReader(); while (OleDbReader.Read()) { OleDbReader.GetString(0); } endTick = Environment.TickCount; Console.WriteLine(String.Format("Round {0} Time: {1} ms", j, endTick - startTick)); OleDb += (endTick - startTick); OleDbConn.Close(); } Console.WriteLine(String.Format("Avg Time: {0} ms", OleDb / 10.0));
Los tiempos para OleDB son los siguientes:
Managed Code And OLEDB Round 0 Time: 35334 ms Round 1 Time: 34492 ms Round 2 Time: 34913 ms Round 3 Time: 33961 ms Round 4 Time: 33618 ms Round 5 Time: 33634 ms Round 6 Time: 34242 ms Round 7 Time: 34024 ms Round 8 Time: 33962 ms Round 9 Time: 34040 ms Avg Time: 34222 ms
El tiempo medio es de 34.2 segundos, mucho más alto que el SQLClient.
Como en las otras pruebas usaremos el mismo código cambiando solo las clases relativas al proveedor, el código usado es:
Console.WriteLine("Managed Code and ODBC");
for (int j = 0; j < 10; j++) { OdbcConnection OdbcConn = new OdbcConnection("Driver={SQL Server Native Client 10.0};Server=PGAVELA-02\\KAWORU;Database=PerfTest;Trusted_Connection=yes"); OdbcCommand OdbcCmd = new OdbcCommand("SELECT TOP 5000000 * FROM HashTable"); OdbcCmd.CommandTimeout = 0; OdbcCmd.Connection = OdbcConn; OdbcConn.Open(); startTick = Environment.TickCount; OdbcCmd.Prepare(); OdbcDataReader OdbcReader = OdbcCmd.ExecuteReader(); while (OdbcReader.Read()) { OdbcReader.GetString(0); } endTick = Environment.TickCount; Console.WriteLine(String.Format("Round {0} Time: {1} ms", j, endTick - startTick)); Odbc += (endTick - startTick); OdbcConn.Close(); } Console.WriteLine(String.Format("Avg Time: {0} ms", Odbc / 10.0));
Los tiempos para este proveedor son los siguientes:
Managed Code and ODBC Round 0 Time: 23603 ms Round 1 Time: 23572 ms Round 2 Time: 23697 ms Round 3 Time: 23603 ms Round 4 Time: 23821 ms Round 5 Time: 23634 ms Round 6 Time: 23587 ms Round 7 Time: 23619 ms Round 8 Time: 23728 ms Round 9 Time: 23462 ms Avg Time: 23632,6 ms
Como podemos ver el tiempo medio es de 23.6 segundos.
Como podemos ver el método que más rendimiento ofrece SQLClient sin ninguna duda, seguido de ODBC. OLEDB se queda con mucho retraso en último lugar. Además el proveedor OLEDB para SQL Server se considera deprecado (http://blogs.msdn.com/b/sqlnativeclient/archive/2011/08/29/microsoft-is-aligning-with-odbc-for-native-relational-data-access.aspx) por tanto no recomendamos utilizar OLEDB para nuevos desarrollos.
Ahora entre SQLClient o ODBC con cual nos quedamos. Si necesitamos rendimiento sin ninguna duda optaremos por SQLClient.
Dentro de SQL Server, la herramienta SQL Profiler es una gran aliada para poder capturar, entre otras cosas, que consultas son las mas costosas. Pero esta herramienta tiene un coste en recursos alto.
Quiero compartir con vosotros ciertas practicas con las que podremos minimizar esta carga:
- Si podemos, utilizaremos Extended Events (a partir de SQL Server 2008) en vez de utilizar SQL Trace o SQL Profiler en entornos productivos. La utilización de recursos por parte de SQL Trace, o SQL Profiler se incrementa al aumentar el numero de Cores de la maquina. - Si necesitamos capturar una traza en un entorno productivo, debemos limitar el numero de eventos a un mínimo. Elige y comprueba cuidadosamente los eventos capturados bajo carga, e intenta evitar combinaciones que aumenten significativamente la carga - Si tenemos habilitado Hyper-Threading, tenemos que tener cuidado con el siguiente contador: Context Switches/sec Este contador nos podrá indicar como de saturados están los cores de cada procesador. Un valor alto (>5000 context swithces por segundo) muestra que estamos saturando la CPU - La captura de SQL Profiler incrementa la carga del sistema entre un 15 y un 25%, generalmente. Si utilizamos los stored procedures (SQL Trace), el impacto es menor. Estos porcentajes dependerán de que estemos capturando, la carga de la instancia, el sistema, y como está siendo usada la instancia. - Evita capturar eventos que ocurren frecuentemente. Si es posible, ajusta la captura con eventos específicos y filtros. Si se capturan menos eventos, se necesitan menos recursos - Enfoca la traza para capturar solo eventos que recojan información relevante para nuestro problema. Por ejemplo, si deseas capturar deadlocks, podemos incluir el evento Lock:Deadlock, evitando el evento Lock:Acquired. Si incluimos ambos eventos, la traza tiene que responder a todos los bloqueos que son adquiridos, y el coste de ejecución puede doblarse. - Evita recolectar datos duplicados. Por ejemplo, podemos evitar recoger SQL:BatchStarted y SQL:BatchCompleted, recogiendo solo SQL:BatchCompleted, que nos mostrara toda la informacion que tiene SQL:BatchStarted - Utiliza filtros en la definición de traza. Por ejemplo, si sabes que el problema lo está teniendo un usuario especifico, crea un filtro por el nombre de usuario.
Links interesantes:
Optimizing SQL Trace http://msdn.microsoft.com/en-us/library/ms187023(v=sql.105).aspx
Best practices for running SQL Server on computers that have more than 64 CPUs http://msdn.microsoft.com/en-us/library/ee210547(v=sql.105).aspx
Optimizing SQL Server CPU Performance http://technet.microsoft.com/en-us/magazine/2007.10.sqlcpu.aspx
Introducing Extended events http://msdn.microsoft.com/en-us/library/bb630354(v=sql.105).aspx
Moisés Romero Senosiain – Microsoft Customer Support Services
Hoy os voy a hablar sobre el proceso de actualización de SQL Server 2005, en un entorno de cluster, aunque ciertos pasos también aplican en un entorno StandAlone.
El proceso de actualización dentro de esta versión de SQL Server es un proceso complejo, donde podemos encontrar diferentes puntos de fallo, de los que suelen poder evitarse muchos, siguiendo las siguientes recomendaciones.
Tareas previas:
1.- Como tarea preventiva, y debido a que esto podría retrasarnos la instalación, vamos a comprobar que los ficheros MSI/MSP que se encuentran disponibles en cada uno de los nodos, son los necesarios para el proceso de actualización:
Para ello, accederemos a la siguiente URL, http://support.microsoft.com/kb/969052/en-us
Y generaremos el script FindSQLInstallsOnly.vbs
Una vez generado, lanzaremos el script de la siguiente manera:
Cscript FindSQLInstallsOnly.vbs %computername%_sql_install_details.txt
Revisaremos la salida del script buscando que cada uno de los MSI/MSP están presentes en la cache de sistema, si encontramos la siguiente salida:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!! C:\WINDOWS\Installer\XXXXX DOES NOT exist in the Installer cache. !!!!
Tendremos que revisar antes de lanzar el proceso de instalación los paquetes MSI/MSP.
2.- Además, sería aconsejable confirmar que los permisos de los diferentes servicios de SQL Server son correctos.
Para una lista de los mismos, tanto permisos a nivel de sistema, como permisos a nivel NTFS, se aconseja revisar la siguiente lista:
http://msdn.microsoft.com/en-US/library/ms143504(v=sql.90).aspx#Review_NT_rights
*3.- Comprobar con que formato tienen los servicios de SQL Server aplicada la cuenta de usuario que levanta los mismos.
En caso de que el usuario que figura en esta cuenta está del siguiente modo:
usuario@dominio
Modificar por dominio\usuario
Tareas inmediatamente previas:
1.- Comprobaremos que haya backups de las bases de datos de sistema y de usuario, y que son correctos.
*2.- Comprobaremos que desde los 2 nodos, la resolución directa e inversa de los nodos del cluster, más el recurso de cluster, son correctas:
Para ello lanzaremos un “ping” hacia los 3 objetos, más un “ping –a”
• Un nslookup no nos valdría, debido a que podemos tener la resolución en DNS correcta, pero podemos tener alguna entrada en el fichero de host, o alguna entrada en la cache de direcciones que no sea correcto.
*3.- Comprobaremos que los servicios”Cryptographic Service”, "Remote Registry Service", "Remote Procedure Call Locator", "Remote Procedure Call Service", "Server" y “Task Scheduler” están levantados y funcionando en ambos nodos.
*4.- Comprobaremos que el shared folder “tasks” del nodo pasivo es accesible desde el activo, y que no hay ninguna tarea creada fuera de las habituales (Buscaremos que no existan tareas de “SQL Server Setup”).
*5.- Comprobaremos que podemos acceder desde el nodo1 a la ruta tasks, dentro de la instalación de Windows (como ejemplo, \\nodo2\c$\Windows\tasks) y que podemos escribir en esa ruta, y viceversa con el usuario que lanza el setup.
*6.- Comprobaremos que no existen usuarios conectados a través de escritorio remoto al nodo secundario.
7.- Comprobaremos que el usuario que lanza el setup de SQL Server es administrador local en todas las maquinas involucradas para esa instancia de SQL Server, y que dispone de los siguientes permisos locales como mínimo:
a. Act as Part of the Operating System
b. Bypass Traverse Checking
c. Log on as Batch Job
d. Log on as Service
e. Replace a Process Level Token
8.- Comprobaremos que SQL Server está online.
9.- Tenemos que tener en cuenta que una actualización de SQL Server implica parada de servicio mientras actualiza las BD de sistema.
*10.- Lanzaremos la actualización desde el nodo activo.
Tareas inmediatamente posteriores:
*1.- Una vez realizada la actualización, reiniciaremos el nodo pasivo. Una vez reiniciado, realizaremos un failover sobre este nodo, y reiniciaremos el nodo pendiente de reiniciar.
2.- Comprobamos que la actualización se ha realizado correctamente, comprobando el fichero summary.txt dentro del directorio de setup bootstrap.
Todas las tareas marcadas con un *, son exclusivas de un cluster.
Estamos instalando un nuevo cluster de SQL Server 2008, el primer nodo parece que se ha instalado bien, pero cuando procedemos a instalar el segundo nodo y vamos a través del Wizard de instalación nos encontramos de pronto con lo siguiente:
Nos sale el error de que el servicio de SQL Server Agent no tiene una cuenta de servicio valida, si intentamos editarla no nos deja ya que el textbox está deshabilitado.
Si puslamos “Next” nos aparece el error de la captura y no podemos continuar…
Y ahora que hacemos.
Volvemos al primer nodo donde se ha instalado correctamente SQL Server y abrimos la consola de clúster, veremos lo siguiente:
Falta el recurso del agente de SQL.
Si nos vamos al Configuration manager veremos que el servicio del agente si está instalado.
Si intentamos añadir el recurso manualmente al grupo de clúster podemos encontrarnos con que el recurso del agente no existe.
Por tanto deberemos añadirlo a mano, para ello en una ventana de comandos con permisos de administrador navegamos a C:\Windows\System32 y ejecutamos lo siguiente
Cluster restype "SQL Server Agent" /CREATE /DLL:SQAGTRES.DLL
El siguiente mensaje aparecerá para indicar que el recurso se ha creado correctamente
Resource type ‘SQL Server Agent’ created
Ahora si intentamos añadir el recurso al cluster de SQL Server este aparecerá en la lista.
Lo añadimos y posteriormente abrimos las propiedades con doble click.
Cambiamos el nombre del recurso a algo más significativo como SQL Server Agent (NombreInstancia)
En Advanced Policies nos aseguramos que solo el nodo donde está instalado SQL Server es el posible owner
Vamos a la pestaña de dependencias y hacemos que dependa del servicio de SQL Server
Vamos a la pestaña de propiedades y rellenamos las dos opciones, en VirtualServerName pondremos el nombre virtual de la instancia de SQL Server (En mi caso SQL2008R2). En InstanceName pondremos el nombre de la instancia de SQL Server que estamos instalando (En mi caso KILIMANJARO64)
Pulsamos aceptar e intentamos levantar el recurso del agente. Si todo a ido bien el recurso se pondrá online.
En este punto antes de continuar deberemos modificar el registro. Abrimos regedit y navegamos a la siguiente clave: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL<Version>.<NombreInstancia>\ConfigurationState
Donde <Version> es 10 para SQL 2008 y 10_50 para SQL 2008 R2 y <NombreInstancia> es el nombre de nuestra instancia.
Aquí encontraremos varios entradas que posiblemente estén a 2.
Deberemos cambiar todas ellas a 1
Para evitar posible sorpresas es recomendable lanzar una reparación en este nodo con el instalador de SQL Server (Necesitaremos poner el grupo de cluster de SQL Server offline).
Una vez hecho todo esto volvemos al otro nodo y lanzamos la instalación para añadir un nuevo nodo.
Y esta vez nos dejara proseguir y finalizar la instalación correctamente.
Hoy vamos a comentar que es FILESTREAM, para que vale, los beneficios que nos puede ofrecer, y que componentes lo forman.
Primero, ¿Qué es FILESTREAM?
Es una tecnología diseñada para tratar, dentro de una instancia SQL, ficheros sin perjudicar la instancia.
Esto se realiza con un driver externo, RSFX, que interactuara entre la BD, y un sistema NTFS.
¿Cómo se monta?
Para montar esta tecnología, tendremos 2 pasos a realizar. Primero, activar ese driver, y segundo, permitir a la instancia a comunicarse con este driver.
Activando el driver:
Para habilitar el driver, dentro de SQL Server Configuration Manager, en la pestaña de servicios, elegiremos la instancia donde queremos habilitar esta configuración. Dentro de las propiedades, pestaña FILESTREAM, podemos seleccionar para que queremos tenerlo habilitado, para TSQL, para TSQL y acceso a través de las APIs de Win32, y si queremos que ese driver este activo para recoger conexiones externas. (En un cluster, siempre estará activo para esas conexiones externas)
Si se habilita el acceso a través de las APIs de Win32, generara un share SMB (que, en este caso, no es un FileShare)
Si intentamos acceder a este share, tendremos el siguiente error:
Esto es así, porque debajo del share SMB no hay un sistema de archivos, sino un EndPoint del driver de RSFX.
El siguiente paso, es habilitar la instancia para que pueda comunicarse con el driver. Para ello, dentro de SQL Server Manager Studio, lo habilitaremos:
Una vez reiniciada la instancia, tendremos FILESTREAM listo para usarse.
A partir de aquí, podremos crear un directorio FILESTREAM dentro de una BD, crear una tabla que utilice esta tecnología, y trabajar con ella, utilizando para ello, o TSQL, o llamadas a la API de Win32 para acceder a ella.
Los permisos necesarios son,
Dentro del sistema NTFS, que el usuario que arranca la instancia, tenga acceso al directorio, y permisos de modificación. Se recomienda que nadie más tenga acceso a este directorio, ya que una modificación externa, implicara que se detecte una corrupción de datos.
Para poder modificar estos ficheros, solo necesitaremos que el usuario que lanza esta modificación / creación / borrado, tenga los permisos DML necesarios (update, select, insert o delete)
Tendréis más información en los siguientes links:
WhitePaper de FILESTREAM:
http://msdn.microsoft.com/en-us/library/cc949109.aspx
Como crear una BD con FILESTREAM:
http://msdn.microsoft.com/en-us/library/cc645585.aspx
Como crear una Tabla con FILESTREAM:
http://msdn.microsoft.com/en-us/library/cc645583.aspx
Uso de FILESTREAM a través de TSQL:
http://msdn.microsoft.com/en-us/library/cc645962.aspx
Uso de FILESTREAM a través de APIs Win32:
http://msdn.microsoft.com/en-us/library/cc645940.aspx
Los síntomas de recibir el siguiente error al intentar ejecutar informes creados en CRM con el asistente ó utilizando MSCRM_FetchDataSource:
En el servidor de SSRS: Your browser does not support scripts or has been configured not to allow scripts. En las máquinas clientes: An error occurred during client rendering. An error has occurred during report processing. Cannot create a connection to data source ‘CRM’. For more information about this error navigate to the report server on the local server machine, or enable remote errors. En CRM: Reporting Error The report cannot be displayed. (rsProcessingAborted)
En el servidor de SSRS: Your browser does not support scripts or has been configured not to allow scripts. En las máquinas clientes: An error occurred during client rendering. An error has occurred during report processing. Cannot create a connection to data source ‘CRM’. For more information about this error navigate to the report server on the local server machine, or enable remote errors. En CRM:
Reporting Error The report cannot be displayed. (rsProcessingAborted)
Pueden tener las siguientes causas:
1. SPN's no configurados 2. Falta de permisos de la cuenta de servicio de SSRS Service en el AD y en SQL 3. Site de CRM ApplicationHost.config no configurado para utilizar las credenciales de Windows Authentication del AppPoolCredentials
Prueba las alguna de las siguientes alternativas para solucionarlo:
1. Configurar los SPNs en el servidor especificado:
Servidor CRM SETSPN -a HTTP/CrmServerName Domain\CRMAppPool SETSPN -a HTTP/CrmServerName.Domain.com Domain\CRMAppPool Servidor SSRS/SQL Server: SETSPN -a HTTP/SsrsServerName Domain\SRSAppPool SETSPN -a HTTP/SsrsServerName.Domain.com Domain\SRSAppPool Para SQL: SETSPN -a MSSQLSvc/SqlServerName:1433 Domain\SQLServerSvcAcct SETSPN -a MSSQLSvc/SqlServerName.Domain.com:1433 Domain\SQLServerSvcAcct
2. Configurar los permisos de la cuenta de servicio de SSRS Service en el AD y modificar los permisos del grupo SQLAccessGroup en SQL como dice el artículo:
Error message when you run a report in Microsoft Dynamics CRM 4.0: "Reporting Error. The report cannot be displayed" http://support.microsoft.com/kb/946585 Sección: Complete the following steps if SQL Reporting Services is running as a domain user account
3. Añadir/cambiar la siguiente sección el fichero ApplicationHost.config del site de CRM Site en un editor de texto . (%windir%\system32\inetsrv\config\. Para todos los ficheros bajo la ruta del Default Web Site)
Poner el elemento WindowsAuthentication del atributo useAppPoolCredentials a true.
Por ejemplo: <system.webServer> <security> <authentication> <windowsAuthentication enabled="true" useAppPoolCredentials="true" /> </authentication> </security> </system.webServer>
Por ejemplo:
<system.webServer>
<security>
<authentication>
<windowsAuthentication enabled="true" useAppPoolCredentials="true" />
</authentication>
</security>
</system.webServer>
Si el servidor de CRM 2012 esta configurado en SQL Cluster habrá que añadir un par de SPNs más como explica el siguiente artículo:
Microsoft Dynamics CRM 2011: Reports created by report wizard may fail when executing
http://support.microsoft.com/kb/2590774
Ingeniero de Soporte de Reporting Services
Supongamos que nos encontramos en la siguiente situación:
En nuestro entorno tenemos montado Log shipping. Estamos utilizando logins de SQL Server, por lo que podemos tener usuarios huérfanos.
Queremos corregir este problema, pero no queremos romper el log shipping. ¿Qué podemos hacer?
Primero, vamos a entender que es un usuario de base de datos, que es un login, como se genera un usuario huérfano, y cuál es la relación entre un usuario de BD y un login:
¿Qué es un login?
Un login es una entidad que accede a la instancia de SQL Server. Esto puede ser un usuario de dominio, un usuario local del sistema, o un usuario de SQL Server. Con esto, podremos entrar a nivel de instancia, aunque no necesariamente a ninguna BD que este dentro de esta instancia.
Para acceder a una BD, tendremos que ser parte de alguno de los roles que otorgan acceso a las BD, o bien tener un usuario de BD mapeado a nuestro login.
¿Qué es un usuario de BD?
Un usuario de BD es la entidad que trabajara a nivel de BD, que puede tener ciertos privilegios a ciertos objetos dentro de esta misma BD.
¿Cuál es la posible relación entre un usuario de BD y un login?
Cuando se relacionan login y usuario, lo hacen a través de un identificador único, el SID. Este SID puede ser generado a nivel de dominio, a nivel de máquina, o bien a nivel de instancia.
Podréis encontrar más información en la siguiente URL:
http://msdn.microsoft.com/es-es/library/ms181127.aspx
Una vez visto esto, vamos a comprobar si tenemos usuarios huérfanos entre las instancias que trabajan en el logshipping:
Primero, comprobamos que en la instancia principal, nuestro Login “Orphaned”, tiene mapeado el usuario de BD:
Y comprobamos que el SID que une al usuario de BD “test”, y al login “Orphaned” es el mismo:
Por lo que, debido a cómo funciona “Log Shipping”, en la BD secundaria, tendremos este mismo SID asociado a nuestro usuario de BD, pero, nuestro login tendrá un diferente SID:
Es decir, que si rompemos el logshipping, veremos que nuestro login “Orphaned” no tendrá acceso a la BD “LogShipping_test”.
Si intento entrar con el login “Orphaned” dentro de la BD recién recuperada, recibiré el siguiente error:
Posibles soluciones para no romper el LogShipping:
Crear el login en la instancia secundaria con el mismo SID que la primaria, añadiendo la cláusula “SID”, y poniendo el mismo SID que el login de la instancia primaria:
En esta pantalla podemos observar que el login “Orphaned” ya tiene acceso a nuestra BD.
Esto es muy útil si, por cualquier razón, no podemos modificar el usuario de BD. Aunque, al borrar el login, cuando lo creemos, tendremos que darle todos los permisos que tuviese antes a nivel de instancia, y corregir cualquier otro mapeo de usuario que tenga.
Si pudiéramos modificar la BD, podremos lanzar el siguiente comando:
USE <database_name>;
GO
sp_change_users_login @Action='update_one', @UserNamePattern='<database_user>',
@LoginName='<login_name>';
Un poco mas de información:
http://msdn.microsoft.com/es-es/library/ms175475(v=sql.105).aspx
INTRODUCCION.-
Muchas veces nos hemos encontrado cuando desarrollamos procedimientos almacenados la necesidad de poder crear una versión de uno previamente creado.
A la hora de versionar un procedimiento almacenado nos encontramos con diferentes problemas, como por ejemplo:
PEQUEÑO TRUCO.-
Vamos a establecer un ejemplo, de cómo, SQL SERVER puede ayudarnos a organizarlos y cómo puede a la hora de crear el script sólo ver un procedimiento almacenado con sus diferentes versiones.
Lo primero vamos a crear un procedimiento almacenado simple:
CREATE PROCEDURE [dbo].[spVersion] AS BEGIN SELECT 'v1' END
Si ejecutamos el procedimiento almacenado, por ejemplo, EXEC spVersion obtenemos el valor v1.
Ahora crearemos otro procedimiento con el mismo nombre pero esta vez vamos a añadir al nombre el signo ; y un número de versión, por ejemplo, el (Inicialmente la versión 1 ya está reservada para la primera definición del procedimiento ).
CREATE PROCEDURE [dbo].[spVersion];2 AS BEGIN SET NOCOUNT ON;
SELECT 'v2' END
Si ejecutamos el procedimiento EXEC spVersion volvemos a obtener el valor ‘v1’ y si ejecutamos el procedimiento EXEC spVersion;2 obtenemos el valor ‘v2’.
Si abrimos el SQL SERVER Management Studio y expandimos los procedimientos almacenados, vemos que sólo existe un procedimiento almacenado.
Y si accedemos a su definición veremos el siguiente resultado.
Lo cual nos permite ir realizando los diferentes versionados del procedimiento almacenado.
José Manuel Jurado Diaz.
Ingeniero de Soporte de SQL SERVER