Fala, pessoal!
Aqui vai uma dica rápida, mas muito útil para nosso dia a dia. Ainda vejo muitas dúvidas por aí sobre este evento, apesar de ser um evento bem raro de encontrarmos hoje em dia, até pelo fato das tecnologias e das formas de acesso ao banco de dados estarem mudando muito nos últimos tempos, ainda nos deparamos algumas vezes com esse tipo de ocorrência.
Você já se deparou com processos no SQL Server utilizando o mesmo SPID (Session_ID) e executando comandos diferentes?
Bom, para começar precisamos ter um entendimento básico do que são conexões, sessões e requisições. Para isso, ilustrarei com a imagem abaixo:
Observação: Essa é uma imagem que eu criei para ilustrar de acordo com o meu entendimento.
Fluxo lógico de como as coisas acontecem (não necessariamente seguirá esta ordem – existem exceções):
- Client da máquina de aplicação (ADO, por exemplo) abre uma conexão física com o SQL Server.
- Após ser enviado um comando ao SQL Server, é criada a conexão lógica. A conexão pode ser estabelecida pelos protocolos TCP, Shared Memory ou Named pipes (sys.dm_exec_connections).
Neste momento é criada uma sessão, mesmo que nada tenha sido executado (sys.dm_exec_sessions).
- Criada a requisição para atender o comando solicitado (sys.dm_exec_requests).
- Criada a transação na (sys.dm_tran_session_transactions).
Normalmente existe apenas uma requisição por conexão e sessão, então acabamos tendo a sensação de que isso é uma regra no SQL Server, mas não é. Existem casos onde você verá sessões sem vínculo com conexões (sessões internas do SQL Server), casos onde você verá conexões sem sessões (normalmente internas do SQL Server, sem propósito de trocar pacotes TDS) e você também vai encontrar sessões com mais de uma requisição aberta e com mesmo session_ID, conhecido como MARS. É o que vou mostrar neste artigo.
Abaixo uma imagem onde apresenta duas conexões vinculadas a mesma sessão e com duas requisições diferentes ativas:
No caso, apenas uma conexão física foi estabelecida. A segunda conexão é uma representação lógica no SQL Server para controle da requisição, mas perceba que apenas uma sessão é mantida. A sessão também é uma representação lógica, ela armazena algumas configurações como tipo de isolamento, set options, lock timeout, entre outras que podemos visualizar na DMV sys.dm_exec_sessions.
Você poderia utilizar, também, a sys.sysprocesses para visualizar esse evento. Lá em cima, na representação do fluxo, eu não citei a view ‘sys.sysprocesses’, pois ela foi marcada como depreciada, e provavelmente será removida em versões futuras do SQL Server, mas você pode utilizar normalmente ou substituir pelas views que compõem suas informações:
sys.dm_exec_connections
sys.dm_exec_sessions
sys.dm_exec_requests
Quando se deparar com esse tipo de evento, saiba que não é um BUG e também não é um problema do SQL Server (talvez na modelagem da aplicação), MARS (Multiple Active Result sets) é um recurso do .NET que em conjunto com o SQL Server permite que você execute vários lotes de comandos no mesmo contexto de conexão. Ou seja, abrir duas ou mais requisições simultâneas no SQL Server com a mesma sessão\conexão. Com isso encontraremos o fenômeno mostrado na imagem acima.
Para utilização do MARS, deve ser habilitado à nível de conexão, exemplo onde o desenvolvedor na mesma conexão e no mesmo contexto queira fazer duas queries diferentes, como no exemplo abaixo.
String de Conexão C#:
“Data Source = DESKTOP-A7S2JPV\\SQLSERVER2016; Database = AdventureWorks2016CTP3;MultipleActiveResultSets=False;Application Name = MARSApp; Uid = user; Pwd = 102030; Connect Timeout = 120″;
SqlCommands:
string vendorSQL =
“SELECT BusinessEntityID, a.Name FROM Purchasing.Vendor a”;
string productSQL =
“SELECT Production.Product.Name FROM Production.Product ” +
“INNER JOIN Purchasing.ProductVendor ” +
“ON Production.Product.ProductID = ” +
“Purchasing.ProductVendor.ProductID ” +
“WHERE Purchasing.ProductVendor.BusinessEntityID = @VendorId”;
Nessa aplicação o desenvolvedor deixou o MARS desabilitado e está tentando utilizar a mesma conexão para executar duas operações diferentes. No item 2 ele varre o DataReader de fornecedores e para cada linha retornada pela query de fornecedores ele varre o DataReader de produtos para buscar os produtos vinculados naquele fornecedor, utilizando o “VendorID” retornado pelo Dataset anterior, item 3 da imagem.
Com o MARS desabilitado, o desenvolvedor receberá a seguinte mensagem no momento da compilação:
- “Já existe uma conexão aberta associada a este comando.”
Para contornar este problema, o parâmetro MultipleActiveResultSets=True é aplicado na string de conexão e o SQL Server abrirá mais de um conjunto de resultado. Ou melhor: terá mais de uma requisição com o mesmo Session_ID fazendo coisas diferentes.
Durante a execução do aplicativo você poderá verificar as DMVs abaixo:
select t.text,* from sys.dm_exec_connections s cross apply sys.dm_exec_sql_text(s.most_recent_sql_handle) t where session_id = 56
select * from sys.dm_exec_sessions where session_id = 56
select * from sys.dm_exec_requests s where session_id = 56
select * from sys.sysprocesses where spid = 56
Informe o session ID da sessão que será aberta pela aplicação, ou filtre pelo nome da aplicação para facilitar.
No meu projeto de teste (que você poderá baixar logo em seguida), utilizei o comando WAITFOR DELAY para facilitar a visualização dentro do SQL Server, por isso o waittype está como “WAITFOR”.
Nos casos onde eu presenciei ocorrências de utilização do MARS, os desenvolveres estavam utilizando algo parecido com o exemplo que dei acima.
- “Ah, Reginaldo, é a melhor forma de retornar essas informações?”
Na maioria das vezes, não. Podem existir exceções, mas no exemplo que apresentei é o famoso RBAR (ainda muito encontrado em diversas aplicações antigas). Para a maioria dos casos, existem formas de se trazer todas as informações em uma única query ou até mesmo criar um procedimento no SQL Server para cobrir essa necessidade. Nos dias de hoje tenho encontrado poucos casos de utilização de MARS, devido a grande transição para os ORMs, como o Entity Framework, NHibernate, Dapper, entre outros.
Existem também alguns problemas ao se utilizar MARS quando são abertas conexões de escrita. Você poderá sofrer com locks, o que soa mais estranho ainda, você se auto bloqueando, mas pode ocorrer. Se quiser entender um pouco mais sobre o MARS, deixarei algumas referências no final do artigo.
Bom, pessoal. Espero que tenha ficado claro o porquê da utilização do MARS e quando encontrar duas session_id com o mesmo id, mas fazendo coisas diferentes, lembre-se do MARS. Isso pode ser um alerta para rever a modelagem da sua aplicação e entender melhor a necessidade da utilização do MARS. Talvez existam soluções melhores.
Referências
Mapeamento de views do SQL Server:
- https://docs.microsoft.com/en-us/sql/relational-databases/system-tables/mapping-system-tables-to-system-views-transact-sql?view=sql-server-2017
- https://docs.microsoft.com/pt-br/sql/relational-databases/system-dynamic-management-views/sys-dm-exec-sessions-transact-sql?view=sql-server-2017
- https://docs.microsoft.com/pt-br/sql/relational-databases/system-dynamic-management-views/sys-dm-exec-requests-transact-sql?view=sql-server-2017
- https://docs.microsoft.com/pt-br/sql/relational-databases/system-dynamic-management-views/sys-dm-exec-connections-transact-sql?view=sql-server-2017
- https://docs.microsoft.com/pt-br/sql/relational-databases/system-compatibility-views/sys-sysprocesses-transact-sql?view=sql-server-2017
RBAR
Connection vs Session
MARS
https://docs.microsoft.com/pt-br/dotnet/framework/data/adonet/sql/manipulating-data
Se você quiser brincar com o projeto em C# para fazer seus testes, segue o código para download:
Bons estudos, um abraço e até o próximo artigo!