Concorrência em aplicação multi-usuário com Entity Framework

344 views
Skip to first unread message

Luiz Gustavo Jordão Soares

unread,
Aug 6, 2012, 3:06:05 PM8/6/12
to dotnetar...@googlegroups.com
Olá,

Tenho uma aplicação multi usuário e toda a camada do banco foi feita usando o Entity Framework 4.0, porém estou passando por um pequeno probleminha de concorrência no banco (Sql Server 2008 R2).

A aplicação tem um processo muito pesado, que demora em torno de 10 min para ser executado.

Este processo (A), trabalha gravando e atualizando dados em uma tabela (X), e todo este processo é feito dentro de uma transaction "Read Uncommitted", pois muitos cálculos são feitos baseado nos dados que ainda não foram comitados.

Até ae, tudo bem.

Porém um outro processo (B), de curta duração, precisar deletar alguns registros na mesma tabela (X). Este processo é feito dentro de uma transaction "Snapshot".

O problema começa quando um usuário dispara o processo (A) e um outro usuário dispara o processo (B). O processo (A) causa locks na tabela (X), impedindo que o processo (B) faça qualquer tipo de deleção na mesma tabela, mesmo os registros a serem deletados não intereferirem nos dados que estão sendo processados no processo (A).

Desculpa por essa pergunta ser meio óbvia, mas será que tem algo de errado na minha arquiterura?

Alguém tem alguma sugestão para esta concorrência?

Desde já agradeço a ajuda.

Luiz Gustavo





Francisco Berrocal

unread,
Aug 6, 2012, 8:49:22 PM8/6/12
to dotnetar...@googlegroups.com
Já tentou configurar o nível de transação?

entidades.ExecuteStoreCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");

João Bateloche

unread,
Aug 6, 2012, 9:20:11 PM8/6/12
to dotnetar...@googlegroups.com
Cara, eu acho que dois processos tão pesados não deveriam rodar sem controle.

Até porque isso pode afetar a utilização do sistema em real time. Acho que o ideal seria que quando o usuário solicitasse a execução de um dos dois processos estes fossem colocados em uma fila, e quando o primeiro terminasse um gerenciador disparasse o segundo.

Você também pode receber uma Action na chamada do scheduler para tratar o término do processamento.



Em 6 de agosto de 2012 21:49, Francisco Berrocal <fran...@gmail.com> escreveu:
Já tentou configurar o nível de transação?

entidades.ExecuteStoreCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");

--
Você recebeu esta mensagem porque faz parte do grupo .Net Architects hospedado no Google Groups.
Para postar envie uma mensagem para dotnetar...@googlegroups.com
Para sair do grupo envie uma mensagem para dotnetarchitec...@googlegroups.com
Para mais opções visite o grupo em http://groups.google.com/group/dotnetarchitects?hl=pt-br

Gustavo Cruz

unread,
Aug 7, 2012, 1:59:36 PM8/7/12
to dotnetar...@googlegroups.com
Concordo com o João.
Dá pra separar e gerenciar bem essas rotinas usando Tasks....




2012/8/6 João Bateloche <jbate...@gmail.com>

Eric Lemes

unread,
Aug 12, 2012, 10:42:01 AM8/12/12
to dotnetar...@googlegroups.com
Luiz Gustavo,

Desculpe a demora...

Independente de como você vai estruturar o código da tua aplicação, me parece que vc tem um problema em como o banco é acessado. 

A questão é que o SQL Server tem um péssimo modelo de concorrência (pelo menos até a versão 2005, não sei se depois disso melhorou, não consegui me aprofundar nos estudos).

A questão é a seguinte: Quando uma transação é aberta no SQL Server, ele marca o registro na página de dados como lockado e tira um "backup" dele no logfile. Enquanto vc altera o registro, ele mexe direto no arquivo de dados. Se vc dá um commit na transação, ele desmarca o "lockado" e limpa o logfile. Se vc dá um rollback ele pega os dados do logfile e salva na página de dados. Mesmo q vc não use uma transação, dê apenas um update ou um insert, internamente o SQL usa o conceito de "transação implícita" e está criando trabalhando com o lock da forma descrita acima. 

Quando um usuário está dando um "select" num registro com lock, em geral ele vai esperar o fim da transação pra poder dar a resposta. 

Acredito que no "snapshot isolation" isso mude, mas nunca vi isso funcionando num cenário real de carga e concorrência pra poder te dizer se é bom ou não.

Outros bancos como o Oracle (na versão 8.0.5, em 1998 já era assim) tem o conceito de "rollback segment". Quando vc abre uma transação, ele escreve os dados modificados no rollback segment, deixando a página de dados livre para ser lida. Qualquer select (mesmo read commited) não espera o término da transação.

Em resumo, no teu problema o processo pesado fica atualizando e os demais ficam esperando porque estão buscando ler registros commitados. Vc duas saídas: perder consistência e usar dirty read (o famoso "NoLock") ou replanejar a forma de atualizar o seu banco, fazer atualizações menores.

Tem mais um ingrediente pra complicar isso ainda... o SQL tem um mecanismo de "escalar" locks, ou seja, se vc manda lockar um registro, ele locka apenas o registro. Se muitos registros próximos estão lockados, ele locka toda a página de dados. Se muitas páginas de dados estão lockadas, ele locka toda a tabela. Se acontece isso em produção, é uma delícia para debugar...

O mesmo vale para índices.

Como existe esse mecanismo de atualizar direto na página, o SQL Server gera locks para leituras. Ou seja, enquanto alguém tá lendo o dado, outro não pode atualizar. Em outras palavras, ele gera lock para select. Eu já tive o desprazer de ver deadlock em selects com SQL Server por causa de locks de leitura em casos em que um select precisa de um índice mais dos dados, o outro dos dados depois do índice. É insano.

Espero ter ajudado.


Abraço,

Eric


2012/8/6 Luiz Gustavo Jordão Soares <luizgusta...@gmail.com>

Luiz Gustavo





Reply all
Reply to author
Forward
0 new messages