Newsgroup: it.comp.lang.visual-basic
Titolo: Gestione delle concorrenza con ADO e SQLServer2000

Salve a tutti, ho un problema con la gestione della concorrenza
nell'utilizzo di ADO e SQLServer2000.
Ho scritto un programma il cui
funzionamento generale č questo.
All'avvio viene mostrato un form di
richiesta username e password in
base al quale il programma identifica
l'utente, in particolare se si
tratta di un amministratore o di un
semplice centralinista. Questa
condizione viene memorizzata in una
variabile globale chiamata uAccesso.
Nel caso di un amministratore, viene
visualizzato il form principale
nel quale, tra gli altri, č presente il
menů Ordine. Se invece si
tratta di un centralinista, il form viene
caricato ma rimane nascosto,
mentre per accedere alle funzioni viene
inserita un'icona nel tray.
Ora nella funzione mnuOrdine_Click c'č una
sola istruzione: frmOrdine.Apri.
Nella funzione Form_MouseMove, che viene
richiamata quando si clicca
sull'icona nel tray, c'č il seguente codice:
Private Sub Form_MouseMove(Button As Integer, Shift As Integer, X As
Single, Y As Single)
   'Questa Sub viene richiamata, oltre che quando
si sposta il
   'mouse, anche quando si clicca sull'icona nella traybar.
'In questo caso se il click avviene con il pulsante destro
   'va mostrato
l'apposito menů.
 
   Dim Msg As Long
 
   Msg = X /
Screen.TwipsPerPixelX
   If Msg = WM_LBUTTONDBLCLK Then frmOrdine.Apri
End Sub
Insomma, in entambi i casi alla fine viene richiamato il metodo
Apri
di frmOrdine. Questo č il codice del metodo.
Public Sub Apri()
Dim sSQL As String
 
   On Error GoTo Apri_Errore
   'IDContatto
variabile privata del form
   'TrovaContatto si occupa di restituire l'ID
del contatto da mostrare.
   IDContatto = TrovaContatto
   If IDContatto
= 0 Then
      MsgBox "Non ci sono persone da contattare.", vbInformation
Unload Me
   Else
      Set cnDati = New Connection
cnDati.IsolationLevel = adXactIsolated
      cnDati.Open sConnStr
Set rsContatti = New Recordset
      sSQL = "SELECT persona, inserito_da,
data_ora_primo_contatto FROM contatti WHERE ID = " & IDContatto
rsContatti.Open sSQL, cnDati, adOpenDynamic, adLockPessimistic, adCmdText
cnDati.BeginTrans
->    lblContatto = "Contatto del " &
rsContatti!data_ora_primo_contatto & " effettuato da " &
rsContatti!inserito_da
      If uAccesso = acCentralino Then Show Else
Show vbModal
      cnDati.CommitTrans
      rsContatti.Close
cnDati.Close
      Set cnDati = Nothing
   End If
   Exit Sub
Apri_Errore:
   cnDati.RollbackTrans
   If rsContatti.State = adStateOpen
Then rsContatti.Close
   If cnDati.State = adStateOpen Then cnDati.Close
MsgBox "Errore " & Err.Number & " nella procedura frmOrdine." & _
"Apri." & vbCrLf & vbCrLf & Err.Description, vbCritical
   Unload Me
End Sub
Lo scopo di questo codice č il seguente.
Il primo che lancia il
metodo, riceve l'id del contatto e ne legge i dati.
Il secondo che lancia
il metodo, riceve lo stesso id, ma quando cerca di
leggerne i dati deve
venir bloccato o, meglio ancora, ricevere
un messaggio di errore.
Ora il
metodo come č scritto funziona in questo modo.
Se un amministratore lancia
il metodo, poi nessun altro riesce ad andare
a leggere i dati, esattamente
come dovrebbe essere, ma rimane bloccato
sulla riga indicata con ->.
Se
invece un centralinista lancia il metodo, gli altri non risultano
bloccati, ma riescono a leggere liberamente i dati.
Perchč si comporta
diversamente nei due casi? Il codice che esegue č
sempre lo stesso!
L'unica istruzione in cui faccio distinzione per il
tipo di utente č dopo
il punto in cui si dovrebbe bloccare!
--------------------------------
Inviato via 
http://usenet.iol.it

 nell'utilizzo di ADO e SQLServer2000.
Molto "spannometricamente", visto che non posso fare test ora:
che ci sta a fare una transazione aperta?
ciao
ugo

Ugo Mangini ha scritto:

Serve per fare in modo che le operazioni che seguono,
che in futuro dovranno essere di più, vengano eseguite
con accesso esclusivo.
Nel post mi sembrava che fosse chiaro qual era l'intento finale.
-- 
Samuele Battarra
prova il mio newsreader:
http://digilander.iol.it/battarsa/programmi/news/

Ugo Mangini ha scritto:

Per essere più chiari.
'Lettura
var = rsDati!nome
.
.
.
'Scrittura
rsDati!nome = "ciao"
.
.
.
'Aggiornamento
rsDati.Update
Se il recordset è aperto con lock ottimistico, il record viene bloccato
prima dell'aggiornamento, se è aperto con lock pessimistico viene
bloccato prima della scrittura. Io vorrei che venisse bloccato prima
della lettura.
Per far ciò ho usato una transazione:
cnDati.IsolationLevel = adXactIsolated
cnDati.Open ...
rsDati.Open ..., cnDati
cnDati.BeginTrans
var = rsDati!nome
rsDati!nome = "ciao"
rsDati.Update
cnDati.CommitTrans
rsDati.Close
cnDati.Close
Nel caso specifico non c'era la scrittura, ma solo la lettura.
E il sistema in alcuni casi funziona (quando mi collego come
amministratore), in altri no. E non capisco cosa generi questa
differenza di comportamento.


[...]

Ecco una domanda VERAMENTE interessante! All'inizio sono rimasto perplesso,
perchè in effetti sembrava tutto ok. Una lunga lettura dei books online mi
ha portato a queste conclusioni:
SERIALIZABLE
Places a range lock on the data set, preventing other users from updating or
inserting rows into the data set until the transaction is complete. This is
the most restrictive of the four isolation levels. Because concurrency is
lower, use this option only when necessary. This option has the same effect
as setting HOLDLOCK on all tables in all SELECT statements in a transaction.
questo è l'isolation level di Sql server che tu hai selezionato, e come
noterai ti precisa che mette un lock su TUTTE le table della select. Ma
quando? Quando leggi i dati, poichè Ado non ha l'edit come DAO. E dov'è che
ti si blocca il programma? Alla prima lettura dei dati! quindi secondo me il
blocco deriva dal fatto che c'è qualcuno con la stessa _TABELLA_ aperta e
Sql server si mette in attesa aspettando di poter mettere un lock su TUTTA
la tabella, il che non accadrà probabilmente mai.
L'ipotesi che faccio, sia chiaro solo un'ipotesi, sulla diversità di
comportamento, è che forse visto che il centralinista non ha il diritto di
bloccare la tabella, l'isolation level è inferiore senza che tu te ne
accorga, perchè probabilmente non intercetti gli errori di Ado.
per verificarlo puoi usare l'enterprise manager ed il comando dbcc per
verificare l'effettivo isolation level di una transazione (ti fermi a metà
transazione con il debugger e vai a vedere che sta succedendo).
come isolation level ti suggerisco adXactRepeatableRead, anche perchè a meno
che tu non abbia 1 o max 2 utenti quello che hai selezionato è decisamente
ingestibile.
Hth
Alfonso

 ha scritto nel messaggio
news:9u2kl1$4agi1$1...@ID-89238.news.dfncis.de...
[CUT]
Concordo con l'analisi generale del problema.
Suggerirei inoltre un approccio totalmente diverso:
invece di utilizzare le transazioni come un meccanismo di locking, ovvero
per uno scopo che non č il loro,
ritengo sia piů opportuno applicare una banale logica semaforica.
Anche senza usare vere e proprie tabelle semaforo, potresti banalmente
aggiungere un campo "locked" che viene pilotato dall'accesso
dell'amministratore al dato e viene filtrato dalle query di lettura delle
segretarie con una clausola where locked=0.
Otterresti lo stesso risultato e non scomoderesti le transazioni che sono
materia destinata ad altro uso e dotate di altre implicazioni.
ciao
ugo

 materia destinata ad altro uso e dotate di altre implicazioni.
Caro Ugo,
da un punto di vista strettamente pargmatico, sono completamente d'accordo
con te. con il campo locked farebbe prima e più facilmente.
Ciononostante, rimane molto più interessante l'analisi della gestione
dell'isolation level e dei lock da parte dei dbms client/server, aspetto
quasi sempre ignorato ed invece _critico_ per il funzionamento e le
prestazioni del db.
Insomma, al posto di Samuele farei qualche altro tentativo con l'isolation
level, anche solo per cultura personale.
Va poi anche detto che con un gran numero di utenti la gestione con un campo
locked pone qualche sottile richio di deadlock, o di campi che restano
settati a 1 impedendo a tutti l'accesso...
ciao
Alfonso

 settati a 1 impedendo a tutti l'accesso...
Su questo non c'è dubbio: si tratta pur sempre di tecniche delicate.
Il rischio di deadlock è indubbiamente presente e deve essere tenuto sotto
controllo
sia da parte delle routine di gestione interne all'applicativo, sia con
strumenti di "sblocco"
a disposizione dell'amministratore e/o automatizzati a livello di sp sul
database.
ciao
ugo
p.s. E' un bel po' che non nasceva un bel thread "psico-teorico"... speriamo
che continui...

 ingestibile.
Su questo sono daccordo. Quel livello di isolamento è stato un eccesso di
precauzione.


 
Si, certo, ma questo metodo voglio tenerlo come ultima risorsa.
Preferisco lasciare la gestione dei Lock al server che sicuramente
č piů bravo di me.


 
Magari arrivando alla soluzione del mio problema! :-)


 settati a 1 impedendo a tutti l'accesso...
Be, per questo il rischio di deadlock c'è anche con le transazioni.


 Be, per questo il rischio di deadlock c'č anche con le transazioni.
Sě ma se gestisce tutto Sql Server si accorge del deadlock e killa uno dei
due processi. Non ti č mai capitato il messaggio "Your process has been
selected for a deadlock..." insomma qualcosa del genere? Motivo di piů per
usare le transazioni...
Ciao Alfonso

 precauzione.
Ribadisco il suggerimento di bloccarti a metà transazione con il debug e di
andare ad esaminare la situazione in sql server con dbcc. O al limite puoi
utilizzare una SP che ti logghi in qualche tabella la situazione durante la
transazione
ciao
Alfonso


Concordo: se a livello Sql Server lo user è lo stesso non può comportarsi
diversamente.
... usi Nt authentication o Sql authentication?
La seconda, vero...?
ciao
ugo

 
Mi sembra la seconda anche se non ci giuro, devo controllare.
Comunque mi ero dimenticado di dire una cosa.
Le transazioni non c'entano!
Infatti eliminando le due instruzioni BeginTrans e CommitTrans
il comportamento è esattamente lo stesso.


 writes:
AM> Be,  per  questo   il  rischio  di  deadlock  c'č   anche  con  le
AM> transazioni.  Sě  ma se gestisce  tutto Sql Server si  accorge del
AM> deadlock e killa uno dei due processi.
Non mi pare un buon modo di operare anche se nel breve e` conveniente.
AM> Non ti č mai capitato il messaggio "Your process has been selected
AM> for a deadlock..." insomma qualcosa del genere?
Puta caso che cambino DB e questa feature sparisca.
Forse e` davvero il caso di implementare la soluzione classica al problema
dei lettori e degli scrittori ("tabulata").
-- 
Gian  | main(){printf(&unix["\021%six\012\0"],(unix)["have"]+"fun"-0x60);}
Uberto|                        David Korn, 1987 ioccc best one liner 
Lauri |-------------------------------------------------------------------
(aka saint) >>Vedere X-Nospam nell'header - See X-Nospam field in Header<<

 il comportamento è esattamente lo stesso.
:-)
Quindi c'è qualche problema a livello di login al db.
ciao
ugo

 dei lettori e degli scrittori ("tabulata").
Questa non la conosco, o almeno non con questo nome. Perchè non ci illumini?
LA cosa interessante di questi thread un po' + "teorici" è che si impara
sempre qualcosa di nuovo...

 ha scritto nel messaggio
news:9u4vv7$68lis$1...@ID-96367.news.dfncis.de...
Mi accodo: Samuele ci devi dare informazioni DETTAGLIATE, ogni particolare
può essere quello decisivo...
Ciao
Alfonso

 sul dominio Nt.
Controllato, è la seconda.

> dei lettori e degli scrittori ("tabulata").
AM> Questa non la conosco, o almeno non con questo nome. Perchè non ci
AM> illumini?   LA  cosa  interessante  di  questi  thread  un  po'  +
AM> "teorici" è che si impara sempre qualcosa di nuovo...
Se  sei  cosi`  cortese  da   attendere,  sono  anni  che  non  tratto
l'argomento, mi  ricordo di portarmi  dietro il libriccino di  S.O. su
cui la ho. La cosa e`  esposta in maniera astratta parlando di risorse
da  leggere  o  scrivere  e  pensando a  problemi  che  sorgono  nella
programmazione concorrente, ma mi pare che si applichino anche in caso
di accessi multipli al DB.



Samuele,
sei sicuro che nel caso delle centraliniste la lettura dal db avviene
_effettivamente_?
Perchè magari il fatto che la form è nascosta...
Tu i dati del db li  vedi proprio?
A questo punto posta un po' di codice, faccio qualche prova con Sql server
7...
Ciao
Alfonso

 di accessi multipli al DB.
Se è il Tannenbaum, basta che indichi la pagina:)
ciao
Alfonso

> di accessi multipli al DB.
AM> Se è il Tannenbaum, basta che indichi la pagina:)
No, e` un librino di un prof di Padova molto interessato nelle reti di
Petri.
Purtroppo non posso guardare a breve nel Tannenbaum della mia lei :|


Salve a tutti.
Mettiamo fine alle disquisizioni filosofiche, ho risolto il problema.
Si trattava di una di quelle ca...te grosse come una casa, solo che
nessuno la vedeva perchè alla casa ci stava appoggiato!
Praticamente se riguardate il codice che ho mandato nel primo post,
dopo l'istruzione di lettura trovate questa istruzione:
   If uAccesso = acCentralino Then Show Else Show vbModal
Nel caso di un amministratore il form veniva mostrato modale, bloccando
l'esecuzione del codice e lasciando il recordset aperto, mentre nel
caso dei centralinisti il form veniva mostrato non modale, il codice
continuava quindi a eseguire chiudendo recordset e connessione, quindi
altri centralinisti potevano accedere senza problemi!


   If uAccesso = acCentralino Then Show Else Show vbModal
Lo stesso difetto che ho anch' io: cercare sempre di scrivere le IF su una riga
sola. Se fosse stato formattato cosi':
If uAccesso = acCentralino Then 
	Show vbModeless
Else 
	Show vbModal
End If
forse sarebbe stato piu' facile vederlo.
-- 
ciao, hal
che predica bene, ma razzola male :(


Meglio così, ma... dai retta al consiglio di hal.
ciao
ugo

 nessuno la vedeva perchè alla casa ci stava appoggiato!
[...]
Meglio così...
Resta comunque la questione non secondaria della scelta del giusto isolation
level, e ti suggerisco ti porti il problema _prima_ di andare in produzione,
perchè è una di quelle cose che con 1 o 2 sviluppatori che fanno test non dà
problemi, ma con 10 o 20 utenti...
Ciao
Alfonso