Grimpi IT Blog

marzo 19, 2011

Monitorear el progreso de una operación masiva de INSERT/DELETE sobre una tabla

Filed under: SQL Server, T-SQL — grimpi @ 11:47 pm

Supongamos que queremos monitorear el progreso de una operación intensiva de insert/delete sobre una tabla dentro de una transacción.
La primera opción que uno piensa es hacer un SELECT COUNT(*) FROM Tabla a medida que se ejecuta la operación de insertado
o borrado de datos. Lamentablemente salvo que tengamos un isolation level muy bajo, esta consulta va a quedar bloqueada
hasta que se termine la ejecución de la otra transacción, por lo tanto, no vamos a poder monitorear el progreso de la misma.

Veamos este ejemplo:

INSERT INTO TablaCliente (ID, Nombre, Apellido)
SELECT ID, Nombre, Apellido FROM ServidorLinkeado.dbo.MaestroClientes

Yo quiero copiar el contenido de la tabla MaestroClientes de un servidor externo a mi base de datos. Supongamos que la tabla MaestroClientes contiene 20 millones de registros, lo que significa que aun en optimas condiciones, el proceso seguramente duraría al menos un par de minutos.
Ahora mientras se ejecuta el INSERT de mas arriba, hagamos un SELECT COUNT(*) FROM TablaCliente en otra sesión. Como verán, la consulta queda en espera, hasta que se termine la transacción que realiza el insertado de datos.
Sin embargo, si consultamos la vista de sistema sys.partitions, vamos a encontrar información muy interesante, especialmente en el campo Rows. Consultando esta vista para la tabla que deseamos monitorear a medida que se ejecuta la transacción, vamos a ver que el campo rows de esa vista varia.

Ejemplo:

SELECT object_id, rows FROM sys.partitions WHERE object_id = OBJECT_NAME(‘TablaCliente’)

A diferencia del SELECT COUNT(*) que hacíamos antes, esta consulta no queda bloqueada, pudiendo ser ejecutada varias veces mientras se realiza la transacción de insertado.
Entonces, como hacemos para ver el porcentaje de progreso?

1) Capturamos el total de registros que vamos a insertar:
SELECT COUNT(*) FROM ServidorLinkeado.dbo.MaestroClientes.

2) Capturamos el total de registros que ya existe en la tabla de destino:
SELECT rows FROM sys.partitions WHERE object_id = OBJECT_NAME(‘TablaCliente’)

3) Ejecutamos la transacción que realiza el insertado masivo de datos:
INSERT INTO TablaCliente (ID, Nombre, Apellido)
SELECT ID, Nombre, Apellido FROM ServidorLinkeado.dbo.MaestroClientes

4) Mientras se ejecuta la transacción, abrimos una nueva conexión y ejecutamos reiteradamente esta consulta:
SELECT rows FROM sys.partitions WHERE object_id = OBJECT_NAME(‘TablaCliente’)

 

 

Exactamente aplica la misma lógica, en el caso de que nos interese monitorear una operación de DELETE. La única diferencia es que en la columna rows de la tabla sys.partitions, veremos que su número desciende sucesivamente.

marzo 11, 2011

Cosas piolas que debería traer la nueva versión de SQL Server:

Filed under: Opinion, SQL Server, T-SQL — grimpi @ 2:13 pm

1) Encriptación irreversible por columna (similar al COLUMN ENCRYPT de Oracle). Ideal para almacenar passwords.

2) Agregar la sentencia CREATE OR REPLACE PROCEDURE/VIEW/FUNCTION como ya tienen Oracle o PostgreSQL. De esta manera, con una sola sentencia, nos evitamos verificar si existe o no una vista, store procedure o una función cuando deployamos.

3) CREATE TABLE NuevaTablaCliente AS TableClienteExistente. La posibilidad de crear una nueva tabla copiando la estructura de otra tabla ya existente es una feature muy piola de PostgreSQL.

4) ALTER COLUMN NombreColumn SET IDENTITY ON/OFF. Habilitar/deshabilitar un campo identity sin necesidad de reconstruir toda la tabla.

5) @NuevoParametro TablaCliente.ClienteId%TYPE. Poder declarar un parametro o variable como tipo de dato de una columna, en vez de declarar explicitamente el tipo de dato como hacemos actualmente.

6) Indíces Bitmap como los de Oracle.

7) Una funcion DECODE similar a la de Oracle (para el que no la conoce, es una especie de CASE…WHEN en una funcion).

8) Que no haya que configurar 50 parametros de Windows y DTC para poder correr una transacción distribuida correctamente.

9) Tener una función de conversión y formateo de datos potente, amigable y facil de usar, y no ese engendro de CONVERT.

10) Una función de SPLIT de string nativa para T-SQL que devuelva una variable de tabla.

11) ALTER TABLE para cambiar el orden de una columna en una tabla. Si bien es un poco “polémica” esta feature (se supone que el orden de las columnas no debería importarnos), la realidad es que a veces termina siendo útil. MySql tiene esta posibilidad.

12) Mejorar el error “String or binary data would be truncated.” cuando insertamos o modificamos datos. En que columna? Que valor fue el que tiro error? Si lo sabes, porque SQL Server no me lo queres decir?

13) Que soporte este tipo de sentencias: SELECT * FROM Clientes WHERE (col1, col2) NOT IN (SELECT col1, col2 FROM OtrosClientes). Actualmente el IN o NOT IN solo lo podemos hacer con una sola columna.

Alguna otra sugerencia?

enero 18, 2011

Lo nuevo en SQL Server 2011

Filed under: SQL Server, SQL Server 2011, T-SQL — grimpi @ 2:02 pm

Estuve investigando SQL Server 2011, llamada “Denali”. Actualmente está en estado beta y se puede bajar la CTP desde acá. Antes de bajarla, para poder instalar esta CTP se requiere como minimo Windows Vista SP2. No funciona ni en Windows XP ni en Windows 2003. De todas maneras parece bastante prometedora.
Que tiene de nuevo esta nueva versión? Obviamente muchas, pero hay 2 cosas que realmente me gustaron:

1) Objetos sequences: Quienes trabajen con Oracle o PostgreSQL, sabrán perfectamente que son. Para el que no lo sabe, un sequence es un objeto cuyo valor se autoincrementa cada vez que es consultado.
Es un excelente reemplazo de los identities, ya que es un objeto externo a la tabla.
Dentro de poco veremos las flamewars en blogs argumentando si es mejor o peor usar sequences o identities como campo PK de una tabla.
Sin embargo, la incorporación de este objeto facilitara enormemente la migración de Oracle y PostgreSQL a SQL Server.
Ejemplo:

CREATE SEQUENCE PrimerSequence
START WITH 1
INCREMENT BY 1;
GO

SELECT (NEXT VALUE FOR PrimerSequence)
GO

INSERT INTO Tabla (Id, Nombre)
SELECT NEXT VALUE FOR PrimerSequence, ‘Esteban’
GO

Link:
http://msdn.microsoft.com/en-us/library/ff878058(v=SQL.110).aspx

La única contra que le veo, es la forma en que SQL Server implementó la lectura de los sequences. Escribir NEXT VALUE FOR xxxx es demasiado texto para simplemente para leer un sequence.
En Oracle o en PostgreSQL resulta más legible y compacto hacer lo mismo.

2) Paginación: Ya no hay que inventar subqueries y cosas raras para paginar datos. Con SQL Server 2011 va a ser posible paginar nativamente una query declarando un OFFSET en la cláusula ORDER BY.
Habrá que investigar la performance de esto, pero parece prometedor.
Ejemplo:

SELECT Id, Campo1, Campo2 FROM Tabla
ORDER BY
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY
GO

Maravilloso. Lo único que no entiendo es porque decidieron poner en el ORDER BY la paginación y no en la instrucción TOP, que pareciera ser lo mas lógico. Después de todo se supone que el ORDER BY solo se limita a ordenar datos y no a limitar la cantidad de registros a devolver.
Además lo hicieron demasiado trabajoso. Demasiadas palabras claves para algo tan simple. Hay que escribir OFFSET, ROWS, FETCH, NEXT, ONLY, ROWS nuevamente solamente para limitar un resultado. MySql lo resuelve de manera mucho mas elegante y practica con su cláusula LIMIT (0,10). Simple, claro y elegante.

Link:
http://msdn.microsoft.com/en-us/library/ff878058(v=SQL.110).aspx

ACTUALIZACION: Me acabo de enterar que la manera en que SQL Server implementó la paginacion corresponde a la especificación ANSI SQL:2008.

Hay otras cosas interesantes en SQL Server 2011.  Se agregó un nuevo tipo de indice, llamado “columnstore”, pensado especialmente para escenarios de datawharehousing. Se mejoró notablemente el Full Text Search, la IDE cambió, se agregaron snippets, varias mejoras en BI y SSIS.
Seguramente con el correr de los meses, se irán incorporando nuevas características. Pero ya el hecho de tener paginación nativa y sequences, representa un gran avance para los desarrolladores y en mi opinion, el nuevo indice por columnas en algunos escenarios, va a ser imprescindible. Pero esto lo voy a explicar en otro post.

marzo 21, 2009

Diferencias entre TRUNCATE TABLE y DELETE FROM

Filed under: SQL Server, T-SQL — Etiquetas: , — grimpi @ 1:01 pm

Una duda bastante habitual entre los desarrolladores, es cuál es la diferencia entre un TRUNCATE TABLE y un DELETE FROM TABLE. Este post (y el blog en general) está centrado en SQL Server, sin embargo, la mayoría de las diferencias entre ambas sentencias aplican a cualquier motor de bases de datos (Oracle, MySQL, DB2, etc).
Primero voy a enumerar las diferencias y luego voy a explicar el porqué de dichas diferencias.

TRUNCATE TABLE

DELETE FROM

  • Es una operación DDL.
  • Es una operación DML.
  • No permite el borrado selectivo. TRUNCATE TABLE elimina todo el contenido de la tabla.
  • Permite el borrado selectivo, mediante la clausula WHERE.
  • No se puede ejecutar, si la tabla tiene asociadas, aun si no existiesen registros en la tabla que contiene la FK.
  • Se puede ejecutar si hay FK asociadas a la tabla, pero siempre y cuando no tenga registros asociados o la FK este deshabilitada.
  • Es la forma más rápida de eliminar el contenido de una tabla.
  • Es más lenta.
  • No se activa ningún trigger al ejecutarse (a partir de SQL Server 2005, es posible capturar el evento mediante un DDL trigger, pero a modo de auditoría, no es posible tener acceso a los valores que fueron eliminados).
  • Puede activarse el trigger de ON DELETE y poder determinar que registros están siendo eliminados. siendo eliminados.
  • En caso que la tabla tuviese un campo Identity, se resetea el valor a 1 (o al valor base determinado en el campo).
  • No resetea el valor del campo Identity, en caso que la tabla tuviese uno.
  • TRUNCATE TABLE desasocia (deallocate) las páginas de datos de la tabla.
  • DELETE FROM marca cada registro afectado, como eliminado.
  • El logueo en el transaction log es mínimo. Solo registra el dealloc de las páginas de datos.
  • Loguea cada operación sobre los registros afectados.
  • No puede ser ejecutado si la tabla tiene asociadas vistas indexadas.
  • Se puede ejecutar si la tabla tiene vistas indexadas.

Como podemos ver, TRUNCATE TABLE es bastante más restrictivo que DELETE, al punto que en muchas situaciones, aun si queremos eliminar todo el contenido de la tabla, no podemos hacerlo.

Ahora bien, que es lo que hace TRUNCATE y porque es mas rápido que su “competidor”?
Para eso primero voy a hacer una brevísima introducción a como almacena internamente SQL Server los datos.
En SQL Server, los registros de una tabla, son agrupados en una estructura física de datos, que se llama página. Cada página tiene un tamaño fijo de 8060 bytes, y puede almacenar uno o cientos de registros, dependiendo del tamaño del mismo. Cuando se intenta insertar más registros en una tabla y la página de datos está llena, se crea otra donde se inserta el nuevo registro y así sucesivamente.
El comando TRUNCATE, lo que hace es desasociar (deallocate) las páginas de datos de la tabla, sin alterar los registros en sí mismo, mientras que el DELETE FROM recorre cada uno de los registros y los marca como borrados, por lo tanto, hace muchas más operaciones de I/O, que aumenta exponencialmente en relación con TRUNCATE, a medida que aumenta el tamaño de la tabla.
Otra razón que explica la diferencia de performance, es que TRUNCATE TABLE solo loguea en el transaction log, el deallocate de las paginas con la tabla (por lo tanto, es posible hacer un rollback de un TRUNCATE, cosa que muchos piensan que no), mientras que el DELETE FROM manda al transaction log todos los registros afectados, lo que es obviamente mucho más costoso a nivel recursos de I/O.
Por último, al hacer un TRUNCATE un lockeo sobre la tabla, a diferencia del DELETE FROM que hace un lockeo por pagina o registro, el consumo de memoria para almacenar los objetos lockeados es mucho menor.

febrero 24, 2009

Lo nuevo de SQL Server 2008: MERGE

Filed under: SQL Server, SQL Server 2008, T-SQL — Etiquetas: — grimpi @ 11:22 pm

Otra de las características interesantes que Microsoft incorporó en SQL Server 2008 y que al igual que la sentencia GROUPING SETS, ya existía en otros motores de bases de datos, es la clausula MERGE (de la cual habíamos hablado brevemente antes).
Esta cláusula nos va a permitir definir lógica de combinación para operaciones atómicas de inserción, borradas y actualización de datos.
La idea es comparar 2 conjuntos de datos y detectar las diferencias de datos entre las 2 tablas y en función de eso, ejecutar alguna operación de actualización sobre la tabla.
La clausula MERGE nos sirve básicamente para 2 cosas:

1) Sincronizar los datos de 2 tablas. Supongamos que tenemos 2 bases distintas (Producción y Desarrollo por ejemplo) y queremos sincronizar los datos de una tabla para que queden exactamente iguales. Lo que antes hubiese implicado
algunas sentencias mezcladas con INNER JOIN y NOT EXISTS, ahora es posible resumirlo en una operación atómica mucho
más sencilla y eficiente.

2) La otra razón por la cual podríamos usar MERGE, es cuando tenemos nuevos datos que queremos almacenar en una tabla y no sabemos si la primary key de la tabla ya existe o no, por lo tanto, no sabemos si hacer un UPDATE o un INSERT en la tabla. El famoso IF EXISTS… (tema que ya habíamos visto acá y que es un poquito más complicado de lo que parece a simple vista).

Veamos un ejemplo:

MERGE dbo.Tabla1 AS Target
USING (SELECT ID,Campo1,Campo2,Campo3 FROM dbo.Tabla2) AS Source
ON (Target.ID = Source.ID)
WHEN MATCHED THEN

UPDATE
SET Target.Campo1 = Source.Campo1, Target.Campo2 = Source.Campo2

WHEN NOT MATCHED BY TARGET THEN

INSERT (ID,Campo1,Campo2,Campo3)
VALUES (Source.ID,Source.Campo1,Source.Campo2, Source.Campo3)

WHEN NOT MATCHED BY SOURCE THEN

DELETE;

Realmente creo que es una de las cosas más interesantes que incorporó la nueva versión de SQL Server y es increíble que algo tan útil y tan práctico como esto, no hubiese estado antes.
Es importante entender que la ventaja del MERGE no es una simple cuestión sintáctica que nos permite escribir un par de líneas menos de código. La ventaja real y más importante está en que permite hacer sincronizar 1 tabla en función de una consulta, de manera atómica. Esto significa que si una operación de delete/insert/update falla, se hace un rollback de todo el conjunto de datos y más importante aún, se garantiza en escenarios de alta concurrencia, donde puede haber otros procesos escribiendo en la misma tabla en el mismo instante, no existan incoherencias.

Capturar salida:
Algo fantástico que existe a partir de SQL Server 2005, es la clausula OUTPUT que permite capturar todo lo que sucedió dentro de una operación INSERT/DELETE/UPDATE. En SQL Server 2008 el uso conjunto de MERGE + OUTPUT nos sirve saber que registros fueron modificados y que acción se hizo sobre ese registro (INSERT, UPDATE o DELETE).
La nueva función $action indica que operación se realizó, mientras que los atributos deleted e inserted guardan la información sobre el registro afectado (de la misma manera que funcionan con los triggers).

Ejemplo:

MERGE dbo.Tabla1 AS Target
USING (SELECT ID,Campo1,Campo2,Campo3 FROM dbo.Tabla2) AS Source
ON (Target.ID = Source.ID)
WHEN MATCHED THEN

UPDATE SET Target.Campo1 = Source.Campo1, Target.Campo2 = Source.Campo2

WHEN NOT MATCHED BY TARGET THEN

INSERT (ID,Campo1,Campo2,Campo3)
VALUES (Source.ID,Source.Campo1,Source.Campo2, Source.Campo3)

WHEN NOT MATCHED BY SOURCE THEN

DELETE

OUTPUT $action, deleted.*, inserted.*;

Resultado:

Avanzando en las profundidades del MERGE:
Si vemos el plan de ejecución, podemos encontrar cosas muy interesantes. En primer lugar, aparece un nuevo operador lógico en el plan, que se llama “Clustered Index Merge” y es nuevo de SQL Server 2008. Este operador en función de un conjunto de datos, realiza un delete, insert o update a una tabla usando su respectivo índice clustered. Si la tabla no tuviese un índice clustered, entonces se usaría el también nuevo operador “Table Merge”. Esta es una de las razones por la cual MERGE no es una simple encapsulación que simplifica cosas que ya podíamos hacer antes, sino que al ser una operación interna del motor de SQL Server, es más eficiente que cualquier otra alternativa existente en las versiones previas de SQL Server.
Otro punto interesante es que podemos ver es que cuando existen índices y las 2 tablas tienen similares volúmenes de datos, el operador MERGE JOIN resulta el método más eficiente en estas operaciones, ya ambas tablas son escaneadas una sola vez y no hay necesidad de ordenar los datos. Es posible cambiar estableciendo otro join hint, pero en la mayoría de los casos, el MERGE JOIN es lo más óptimo.


Algunas recomendaciones para obtener mejor performance con esta sentencia:

  • Crear un índice clustered sobre las columnas relacionadas en el JOIN, en la tabla destino.
  • Crear un índice único sobre las columnas relacionadas en el JOIN, en la tabla de origen (en caso que hubiese).

Esto garantiza al motor que no tiene que ejecutar ninguna validación adicional ni efectuar ningún sort extra.

Links:
http://technet.microsoft.com/en-us/library/cc879317.aspx
http://technet.microsoft.com/en-us/library/bb522522.aspx

noviembre 29, 2008

Lo nuevo de SQL Server 2008: Grouping Sets

Filed under: SQL Server, SQL Server 2008, T-SQL — Etiquetas: , — grimpi @ 4:33 pm

Una característica piola que tiene la nueva versión de SQL Server, es el nuevo operador GROUPING SETS, que ya existía en otros motores como Oracle hace tiempo (es un estándar ANSI 2006) y permite combinar consultas de agrupación distintas en una sola consulta. El operador GROUPING SETS es una extensión de la cláusula estándar GROUP BY.
Cuando no se requieren todas las agrupaciones posibles que se generan utilizando un operador ROLLUP o CUBE (que ya existían en SQL Server 2005), se debe utilizar GROUPING SETS para especificar sólo las agrupaciones que se deseen. O sea, gracias a GROUPING SETS obtenemos los niveles de agrupación deseados y además nos devuelve el subtotal para cada subconjunto de agrupación.
Podríamos decir que GROUPING SET es mas abarcativo y genérico que ROLLUP o CUBE. Generalmente consultas que usen este tipo de operadores están relacionadas con el análisis de datos, reporting y todo lo relacionado con el mundo BI.
Este nuevo operador no permite hacer nada nuevo que antes no se pudiese, solo simplifica y optimiza determinadas consultas, lo ya de por si, es un factor importante.
Veamos un ejemplo de donde utilizarlo:

Supongamos que tenemos la siguiente tabla de Clientes con datos ya cargados:

CREATE TABLE [dbo].[Client]
(

[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](50) NULL,
[Country] [varchar](50) NULL,
[Zone] [varchar](50) NULL,
[Year] [int] NULL,
[Precio] [numeric](12, 4) NULL,

)

Y ahora supongamos que queremos traer la suma del campo Precio, agrupada por los Campos Country y Zone, luego solo por Zone y luego por el total y todo en una sola misma sentencia de tal manera que el resultado de la consulta fuese el siguiente:


La primera opción que uno piensa, es hacer una consulta usando UNION ALL con tres consultas diferentes, una por cada agrupación:

SELECT SUM(Value) AS Total, Zone, Country FROM dbo.Client
GROUP BY Zone,Country

UNION ALL
SELECT SUM(Value), NULL, Country FROM dbo.Client
GROUP BY Country

UNION ALL
SELECT SUM(Value), NULL, NULL FROM dbo.Client
ORDER BY Zone, Country

Sin embargo, podemos escribir una consulta equivalente y acá entra GROUPING SETS de SQL Server 2008 para simplificarnos la vida:

SELECT SUM(value) AS Total, Zone, Country FROM dbo.Client
GROUP BY
GROUPING SETS
(
(Zone, Country),
(Country),
()
)
ORDER BY GROUPING(Zone), GROUPING(Country)

Ahora como podrán ver en este último ejemplo, además de usar el operador GROUPING SETS, hago uso de la función GROUPING en el ORDER BY, para ordenar el resultado por Zona y Country.
Esta función (que ya existía en SQL Server 2005), nos indica si una expresión de columna especificada en una lista GROUP BY es agregada o no. GROUPING devuelve 1 para agregado y 0 para no agregado, en el conjunto de resultados.

Performance:

La ventaja del uso GROUPING SETS no está solo dada por simplificar sintácticamente las consultas, sino también en cuestiones de performance.
En las pruebas que hice para monitorear el uso de recursos (SET STATISTICS IO ON) con 90000 registros en la tabla Client estos fueron los resultados:

Usando UNION ALL:
Table ‘Client’. Scan count 3, logical reads 1728, physical reads 6, read-ahead reads 590, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Usando GROUPING SETS:
Table ‘Client’. Scan count 1, logical reads 576, physical reads 6, read-ahead reads 590, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Como podemos ver, GROUPING SETS hace menor uso de recursos de I/O. La razón de la mejor performance, se debe a que usando la nueva característica de SQL Server, el motor necesita leer menos paginas de datos, ya que hace el cálculo de agregación de más alto nivel sobre las agregaciones de menor nivel (usando como ejemplo nuestra consulta, el nivel de menor de agregación seria Zone y Country, luego solo Country y por último a partir del resultado de la prime).
Dejo la tarea al lector para que compare los planes de ejecución y pueda apreciar las diferencias en las operaciones que realiza el motor entre una consulta y otra.

Links:
Recomiendo leer el articulo de MSDN de equivalencias de GROUPING SETS.

abril 3, 2008

Soluciones para actualizar un registro si existe, sino insertar en SQL Server.

Filed under: SQL Server, T-SQL — grimpi @ 2:06 am

Muchas veces cuando trabajamos con ABMs o algún proceso de escritura en la base de datos, al actualizar los registros, debemos establecer si vamos a efectuar un INSERT o un UPDATE. O sea, tenemos que determinar si el registro existe o no, para saber que operación se va a efectuar en la base de datos.

Generalmente se suele encapsular toda esta lógica dentro de un SP, algo que considero una muy buena practica, ya que nos desentendemos del lado de la aplicación, si se va a efectuar una operación de inserción o de modificación.

Primera solución:

Ahora bien, dentro del Store Procedure, lo solemos hacer para determinar la operación, es el famoso IF EXISTS.
Ejemplo:

IF EXISTS(SELECT ID FROM TABLA WHERE ID = @ID)
INSERT INTO TABLA (Campo1,ID) VALUES (@Valor,@ID)
ELSE
UPDATE
TABLA SET Campo1 = @Valor WHERE ID = @ID

No es un mal enfoque, es muy claro. Sin embargo esta solución tiene dos inconvenientes:
1) Estamos pagando el costo de ejecutar un Query. Por mas que la consulta este indexada, tiene un costo.
2) No es 100% segura. En entornos muy demandantes, con alta concurrencia, puede darse el caso de que justo luego de ejecutar el IF EXISTS, otro proceso inserte en la tabla un registro con la misma PK y no tendríamos forma de darnos cuenta, generando un error de duplicate key.
Por lo tanto, esta opción que es la más común, tiene serios inconvenientes.

Segunda solución:

Una segunda opción podría ser esta:

UPDATE TABLA SET Campo1 = @Valor WHERE ID = @ID
IF @@ROWCOUNT = 0
INSERT INTO TABLA (Campo1,ID) VALUES (@Valor,@ID)

En caso, se eliminaría el tener que ejecutar una query con el EXISTS. Aunque en caso de que no exista el registro, se ejecuta el UPDATE innecesariamente.
Si la mayoría de las operaciones van a ser del tipo INSERT, en realidad no se ganaría performance, pero por el contrario, si la mayoría de las operaciones seria del tipo UPDATE, podría llegar a ser mas performante.
De todas maneras esta solución sigue teniendo el problema de que otro proceso podría insertar un registro con la misma PK en la tabla y no tendríamos forma de darnos cuenta.

Tercera solución:

En el segundo caso ganamos un poco de performance (no siempre), pero seguimos con el mismo problema de concurrencia.
Pero ahora veamos este ejemplo de código:

BEGIN TRY
INSERT INTO TABLA (Campo1,ID) VALUES (@Valor,@ID)
END TRY
BEGIN CATCH
UPDATE TABLA SET Campo1 = @Valor WHERE ID = @ID
END CATCH

A nivel perfomance, es similar a las otras soluciones, pero si tenemos muchas más operaciones de inserción que de actualización, vamos a ganar velocidad.
Sin embargo, en este caso no tendríamos el inconveniente de concurrencia que sucede en los 2 casos anteriores!!. Lo cual lo hace ideal para situaciones de alta demanda.

Cuarta solución (Solo en SQL Server 2008):

SQL Server 2008, incorpora el comando MERGE (que ya teníamos en Oracle y otros motores), que sirve para resolver de una manera muy eficiente, exactamente este problema.

MERGE TABLA
USING (SELECT @ID AS ID) AS SRC ON SRC.ID = TABLA.ID
WHEN MATCHED THEN
UPDATE SET Campo1 = @Valor
WHEN NOT MATCHED THEN
INSERT (Campo1,ID) VALUES (@Valor,@ID)

Con este método, también solucionamos el problema de concurrencia, y además evitar tener que ejecutar consultas innecesarias. Por lo cual, podríamos decir que es la optima solución resolver este problema, aunque lamentablemente debemos esperar hasta mitad de año, cuando Microsoft libere SQL Server 2008.

Conclusión:

Vimos como un problema en apariencia tonto y trivial, puede causar serios problemas de performance y peor aun, crear errores de concurrencia y comportamientos no deseados.
Por las pruebas que hicimos en un entorno de TEST, la diferencia de performance que hicimos no son demasiadas. Pero en situaciones de alta concurrencia, las 2 primeras soluciones son definitivamente incorrectas.

Recomiendo ver estos links, que explican como funcionan los lockeos, en cada una de las distintas soluciones:

http://weblogs.sqlteam.com/mladenp/archive/2007/07/30/60273.aspx
http://weblogs.sqlteam.com/mladenp/archive/2007/08/03/60277.aspx

Y estas soluciones alternativas al mismo problema, tal vez sean un poco más complejas, pero en algunos escenarios pueden ser útiles:

http://www.samsaffron.com/blog/archive/2007/04/04/14.aspx
http://www.sqlteam.com/article/application-locks-or-mutexes-in-sql-server-2005

Blog de WordPress.com.