¿Es posible que las instrucciones SQL se ejecuten simultáneamente en una sola sesión en SQL Server?

16

He escrito un procedimiento almacenado que hace uso de una tabla temporal. Sé que en SQL Server, las tablas temporales tienen un alcance de sesión. Sin embargo, no he podido encontrar información definitiva sobre exactamente de lo que es capaz una sesión. En particular, si es posible que este procedimiento almacenado se ejecute dos veces simultáneamente en una sola sesión, se requiere un nivel de aislamiento significativamente mayor para una transacción dentro de ese procedimiento debido a que las dos ejecuciones ahora comparten una tabla temporal.

Trevor Giddings
fuente

Respuestas:

19

Si bien la respuesta de Brent es correcta para todos los fines prácticos, y esto no es algo por lo que haya visto preocuparse a alguien, es posible que múltiples invocaciones de un procedimiento almacenado en una sesión se afecten entre sí a través de una tabla #temp con ámbito de sesión .

La buena noticia es que es extremadamente improbable que suceda en la naturaleza porque

1) Las tablas #Temp declaradas dentro de un procedimiento almacenado o lotes anidados en realidad no tienen visibilidad de sesión (o duración). Y estos son, con mucho, el caso más común.

2) Requiere MultipleActiveResultsets y alguna programación de cliente asíncrono muy extraña, o para que el procedimiento almacenado devuelva un conjunto de resultados en el medio, y el cliente llame a otra instancia del procedimiento almacenado mientras procesa los resultados desde el primero.

Aquí hay un ejemplo artificial:

using System;
using System.Data.SqlClient;

namespace ado.nettest
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var con = new SqlConnection("Server=localhost;database=tempdb;integrated security=true;MultipleActiveResultSets = True"))
            {
                con.Open();

                var procDdl = @"
create table #t(id int)
exec ('
create procedure #foo
as
begin
  insert into #t(id) values (1);
  select top 10000 * from sys.messages m, sys.messages m2;
  select count(*) rc from #t;
  delete from #t;
end
');
";
                var cmdDDL = con.CreateCommand();
                cmdDDL.CommandText = procDdl;
                cmdDDL.ExecuteNonQuery();

                var cmd = con.CreateCommand();
                cmd.CommandText = "exec #foo";
                using (var rdr = cmd.ExecuteReader())
                {
                    rdr.Read();

                    var cmd2 = con.CreateCommand();
                    cmd2.CommandText = "exec #foo";
                    using (var rdr2 = cmd2.ExecuteReader())
                    {

                    }

                    while (rdr.Read())
                    {

                    }
                    rdr.NextResult();
                    rdr.Read();
                    var rc = rdr.GetInt32(0);
                    Console.WriteLine($"Numer of rows in temp table {rc}");

                }


            }

            Console.WriteLine("Hit any key to exit");
            Console.ReadKey();
        }
    }
}

que salidas

Numer of rows in temp table 0
Hit any key to exit

porque la segunda invocación del procedimiento almacenado insertó una fila y luego eliminó todas las filas de #t mientras la primera invocación esperaba que el cliente recuperara las filas de su primer conjunto de resultados. Tenga en cuenta que si el primer conjunto de resultados es pequeño, las filas podrían quedar almacenadas en el búfer y la ejecución podría continuar sin enviar nada al cliente.

Si mueves el

create table #t(id int)

en el procedimiento almacenado que produce:

Numer of rows in temp table 1
Hit any key to exit

Y con la tabla temporal declarada dentro del procedimiento, si cambia la segunda consulta a

cmd2.CommandText = "select * from #t";

Falla con:

'Nombre de objeto inválido' #t '.'

Porque una tabla #temp creada dentro de un procedimiento almacenado o lote anidado solo es visible en ese procedimiento almacenado o lote y en procedimientos anidados y lotes que llama, y ​​se destruye cuando finaliza el procedimiento o lote.

David Browne - Microsoft
fuente
12

No concurrentemente. Sus opciones incluyen:

  • Ejecute las consultas una tras otra en la misma sesión
  • Cambie de una tabla temporal a una tabla temporal global (use ## TableName en lugar de #TableName), pero tenga en cuenta que la tabla temporal global se descarta automáticamente cuando se cierra la sesión que creó la tabla temporal, y no hay otras sesiones activas con una referencia a ello
  • Cambie a una tabla de usuario real en TempDB: puede crear tablas allí, pero tenga en cuenta que desaparecerán al reiniciar el servidor
  • Cambiar a una tabla de usuario real en una base de datos de usuario
Brent Ozar
fuente