[SQL Server] Ressources lock bloquées par d'autres processus

Ressources lock bloquées par d'autres processus [SQL Server] - SQL/NoSQL - Programmation

Marsh Posté le 14-01-2004 à 15:21:51    

J'ai un site où, à chaque bas de page, je lance des opérations, de deux types :
- une table des connexions pour que je puisse savoir en temps réel combien sont en ligne actuellement
- un insert dans une table qui me répertorie TOUS les chargements de page, pour que je puisse tout retracer.
 
Pour les afficionados, voici la procédure stockée :
 

Code :
  1. CREATE PROCEDURE dbo.ins_load
  2. @url varchar(500),
  3. @load decimal(10,6),
  4. @admin int,
  5. @user bigint,
  6. @form varchar(8000),
  7. @qs varchar(8000),
  8. @ip varchar(15)
  9. AS
  10. DECLARE @datebutoir smalldatetime
  11. SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED -- forcage locks faibles
  12. BEGIN TRAN
  13. SET @datebutoir = DATEADD(minute, -5, getdate())
  14. -- Suppression vieilles connexions
  15. DELETE FROM connections
  16. WHERE conn_date <= @datebutoir
  17. -- Suppression anciennes connexions anonymous
  18. DELETE FROM connections
  19. WHERE userid IS NULL
  20. AND adm_id IS NULL
  21. AND conn_ip = @ip
  22. -- Suppression anciennes connexions connues
  23. DELETE FROM connections
  24. WHERE (userid IS NOT NULL OR adm_id IS NOT NULL)
  25. AND (userid = @user OR adm_id = @admin)
  26. -- Ajout de la connexion
  27. INSERT INTO connections (conn_ip, adm_id, userid, conn_url)
  28. VALUES (@ip, @admin, @user, @url)
  29. -- Insertion dans la table loads
  30. INSERT INTO LOADS (load_url, load_time, load_admin, load_user, load_form, load_qs, load_ip)
  31. VALUES (@url, @load, @admin, @user, @form, @qs, @ip)
  32. COMMIT TRAN
  33. GO

Mon problème est que j'ai très régulièrement des erreurs :

Citation :

La transaction (ID du processus 103) a été bloquée sur les ressources lock par un autre processus et a été choisie comme victime. Relancez la transaction.


 
Quelqu'un aurait-il une solution pour m'éviter ces erreurs ?

Reply

Marsh Posté le 14-01-2004 à 15:21:51   

Reply

Marsh Posté le 14-01-2004 à 17:44:39    

t'es pas un peu goret toi ?
 
vire-moi ces delete de partout !
crée un trigger "on insert" sur ta table, qui fait un insert ou un update selon les cas.
 
et pour shooter les vieilles cnx inutilisées, y'a un truc qui s'appelle job, et ça se schedule. pas besoin de nettoyer la base à chaque hit.
 
m'enfin surtout, je vois pas trop comment ça marche ton truc, j'ai l'impression que c'est pas terrible. sur le serveur t'as activé les sessions ? si oui, utilise le session_id comme identifiant de connection, ça simplifiera tes traîtements (plus besoin de vider comme un sauvage à coup de delete massifs à chaque fois... d'autant plus que "session_onend" dans le global.asa c'est très bien pour effectuer un delete sur session_id proprement. donc tu n'aura plus qu'à faire un "insert" lors du "session_onstart", un update à chaque page, puis le delete à la fin. avec à chaque fois une seule ligne traîtée.

Reply

Marsh Posté le 14-01-2004 à 17:45:18    

je te poste le système que j'utilise actuellement sur mon site ce soit. il est bien moins lourd, aussi fiable, et permet de faire la même chose.

Reply

Marsh Posté le 14-01-2004 à 19:28:19    

Dans le global.asa :
 


 Sub Session_OnStart
  on error resume next
  set cnx = server.CreateObject("ADODB.Connection" )
  cnx.Open application("connectionString" )
  cnx.execute "NEW_SESSION " & session.SessionID
  cnx.close
  set cnx = nothing
  on error goto 0
 End Sub
 
 Sub Session_OnEnd
  on error resume next
  set cnx = server.CreateObject("ADODB.Connection" )
  cnx.Open application("connectionString" )
  cnx.execute "KILL_SESSION " & session.SessionID
  cnx.close
  set cnx = nothing
  on error goto 0
 End Sub


 
Dans une page d'include, à appeler sur chaque page. Tu peux en faire une PS et ne pas gérer la génération de la liste des connectés ;)
 


sub WhoIsHere()
 if request("ID" ) = "" then
  ID = "NULL"
 else
  ID = request("ID" )
 end if
 
 if session("LOGIN" ) = "" then
  LOGIN = "NULL"
 else
  LOGIN = quote(session("LOGIN" ))
 end if
 curPage = ""
 if request.QueryString("nextpage" ) <> "" then
  curPage = request.QueryString("nextPage" )
 else
  curPage = page
 end if
 cnx.execute "LOG_SESSION " & session.SessionID & ", " & LOGIN & ", " & quote(curPage) & ", " & ID
 sql = "SELECT LOGIN, PAGE, SINCE FROM ( " & _
    "SELECT LOGIN, replace(PAGE, 'oldurl', 'Home') PAGE, DATEDIFF(minute, START_TIME, GetDate()) AS SINCE FROM ACTIVITE WHERE LOGIN IS NOT NULL " & _
    "UNION " & _
    "SELECT 'zzzz' as LOGIN, replace(isNull(PAGE, 'Home'), 'oldurl', 'Home') PAGE, COUNT(START_TIME) AS SINCE FROM ACTIVITE WHERE LOGIN IS NULL GROUP BY replace(isNull(PAGE, 'Home'), 'oldurl', 'Home') " & _
    " ) tmp ORDER BY PAGE"
 dim rsActivity
 set rsActivity = server.CreateObject("ADODB.RecordSet" )
 rsActivity.ActiveConnection = cnx
 rsActivity.Open sql
 pagename = ""
 allusers = 0
 response.write "<table cellspacing=""0"" cellpadding=""0"" border=""0"">" & vbcrlf
 response.write "<tr><td align=""left""><font class=""tips""><b>Utilisateurs connectés</b></font></td></tr>" & vbcrlf
 do while not rsActivity.eof
  if rsActivity("PAGE" ) <> pagename then
   pagename = getPageCoolName(rsActivity("PAGE" ))
   response.write "<tr><td align=""left""><font class=""tips"">Page : " & pagename & "</font></td></tr>" & vbcrlf
  end if
  if rsActivity("LOGIN" ) <> "zzzz" then
   response.write "<tr><td align=""left""><font class=""texte""><a href=""?page=VoirProfil&user=" & rsActivity("LOGIN" ) & """ class=""texte"">" & rsActivity("LOGIN" ) & "</a> (" & rsActivity("SINCE" ) & " min)</font></td></tr>" & vbcrlf
   allusers = allusers + 1
  else
   response.write "<tr><td align=""left""><font class=""texte"">" & rsActivity("SINCE" ) & " anonyme(s)</font></td></tr>" & vbcrlf
   allusers = allusers + rsActivity("SINCE" )
  end if
  rsActivity.MoveNext
 loop
 if allusers <> 0 then
  response.write "<tr><td align=""left""><font class=""texte""><br>Total : " & allusers & "</font></td></tr>" & vbcrlf
 end if
 response.write "</table>" & vbcrlf
 rsActivity.Close
 set rsActivity = nothing
end sub


 
Les PS :
 


CREATE PROCEDURE KILL_SESSION(@SESSIONID bigint) AS
    DELETE ACTIVITE WHERE SESSION_ID = @SESSIONID
GO
 
CREATE PROCEDURE LOG_SESSION(@SESSIONID bigint, @LOGIN varchar(50), @PAGE varchar(50), @ARTICLE int) AS
    UPDATE ACTIVITE SET LOGIN = @LOGIN, PAGE = @PAGE, ARTICLE = @ARTICLE, UPDATE_TIME = GetDate() WHERE SESSION_ID = @SESSIONID
GO
 
CREATE PROCEDURE NEW_SESSION(@SESSIONID bigint) AS
    INSERT INTO ACTIVITE (SESSION_ID, START_TIME, UPDATE_TIME)
    VALUES (@SESSIONID, GetDate(), GetDate())
GO


 
Et zou !
 
Tu peux ajouter un job qui fait un delete pour toutes les lignes de ACTIVITE dont UPDATE_TIME est de plus d'un quart d'heure et le scheduler toutes les 5 minutes si tu veux.


Message édité par MagicBuzz le 14-01-2004 à 19:28:40
Reply

Marsh Posté le 15-01-2004 à 09:19:28    

Le problème des session c'est que ça me donne un résultat exagérément élevé.
 
Là actuellement j'ai 20 utilisateurs en ligne, et 121 sessions actives.
 

Code :
  1. Sub Session_OnStart
  2. ' Set a Session Start Time
  3. ' This is only important to assure we start a session
  4. Session("Start" ) = Now
  5. ' Increase the active visitors count when we start the session
  6. Application.Lock
  7.  Application("ActiveUsers" ) = Application("ActiveUsers" ) + 1
  8. Application.UnLock
  9. End Sub
  10. Sub Session_OnEnd
  11. ' Decrease the active visitors count when the session ends.
  12. Application.Lock
  13.  Application("ActiveUsers" ) = Application("ActiveUsers" ) - 1
  14. Application.UnLock
  15. End Sub


 
C'est probablement parce que le timeout n'est pas le même : 5 minutes sur mes tables de connexion, 20 minutes sur les variables de session.
Je n'ai pas non plus envie de baisser le session.timeout, parce que les clients logués sur le site se feraient bouger à partir de 5 minutes d'inactivité. Donc, je ne peux pas trop me permettre d'utiliser ce système.
 
Peut-être que la bonne solution est de faire un insert à chaque fois (plus un pour loads qui n'est pas supprimable), et de gérer les delete par trigger comme tu le suggères. En plus de cela, gérer les connexions sur trois tables distinctes (une pour les anonymous, une pour les membres logués, une pour les administrateurs), afin de minimiser les risques de locks.

Reply

Marsh Posté le 15-01-2004 à 09:32:21    

Non non, faut pas utiliser ce système là.
Par par la BDD et shoot les lignes qui restent trop longtemps via une requête schédulée.
 
Parcequ'en effet, le Session_OnEnd n'est pas toujours éxécuté, selon les version de IIS (c'est un bug connu depuis longtemps, qui est plus ou moins présent d'une version à l'autre).
 
Si tu shootes toute connection inactive depuis 5 minutes, ça devrait être pas mal, à condition que ce délais soit suppérieur au timeout des sessions (spécifié dans les paramètres de IIS)

Reply

Marsh Posté le 15-01-2004 à 09:34:21    

Mais même si je schedule ce job pour fonctionner une fois par minute.
Durant ce lap de temps, tout chargement de page sera considéré comme une connexion si je fais un insert !?
 
PS : le timeout des sessions est de 20 minutes (plus pour les admin)
PS2 : le "timeout" pour une connexion, ce que je cherche à obtenir, c'est 5 minutes


Message édité par LToPiQ[PPC] le 15-01-2004 à 09:35:24
Reply

Marsh Posté le 15-01-2004 à 09:35:30    

OK, pour la seconde solution, en effet, j'y ai pensé aussi.
 
Abandonne les Session_OnStart et Session_OnEnd
Crée un trigger sur "on insert" sur la table d'activité, qui fait un update si une ligne avec l'ID existe déjà.
 
A chaque chargement de page, insère une ligne dans activité (ou met à jour via trigger).
 
Puis par schedule, shoote toutes les 5 minutes les lignes qui n'ont pas été mises à jour depuis un certan délais.
 
Utilise Session.SessionID comme identifiant, c'est bien mieu que l'IP, dans le cas où deux utilisateurs passent par le même proxy pour accéder au site.


Message édité par MagicBuzz le 15-01-2004 à 09:35:39
Reply

Marsh Posté le 15-01-2004 à 09:37:00    

Ce système te garantis une ligne par connecté, avec une durée de vie de 5 minutes, et la ligne est re-créée dès que l'utilisateur redevient actif, même si sa session n'a pas expiré

Reply

Marsh Posté le 15-01-2004 à 09:42:27    

Si je résume, à chaque chargement de page j'ai :
 INSERT INTO historique des pages
 INSERT INTO connexions
 
Avec un trigger "on insert instead of" de connexions. si EXISTS(Session.SessionID) alors ne pas faire l'insert et faire un update nouvelledate = getdate()
 
Job qui se lance toutes les 5 minutes :
 DELETE connexions WHERE date < DATEADD(minute, -5, getdate())

Reply

Marsh Posté le 15-01-2004 à 09:42:27   

Reply

Marsh Posté le 15-01-2004 à 09:43:25    

Je continue de penser que splitter ma table connexions en trois tables (une pour les anonymous où Session.SessionID est identifiant, l'autre pour les membres connectés où l'identifiant est UserID et l'autre pour les administrateurs où l'identifiant est AdminID) serait également une bonne chose.
Avec des tables plus légères, les risques de locks de plusieurs ressources en même temps est diminué.
 
Ensuite dans mon code, à chaque chargement de page :
 

Code :
  1. If AdminID <> "" then
  2.      Execute PS "admin"
  3. else
  4.      if UserID <> "" then
  5.           Execute PS "user"
  6.      else
  7.           Execute PS "anonymous"
  8. end if


Message édité par LToPiQ[PPC] le 15-01-2004 à 09:50:14
Reply

Marsh Posté le 15-01-2004 à 10:09:17    

LToPiQ[PPC] a écrit :

Si je résume, à chaque chargement de page j'ai :
 INSERT INTO historique des pages
 INSERT INTO connexions
 
Avec un trigger "on insert instead of" de connexions. si EXISTS(Session.SessionID) alors ne pas faire l'insert et faire un update nouvelledate = getdate()
 
Job qui se lance toutes les 5 minutes :
 DELETE connexions WHERE date < DATEADD(minute, -5, getdate())


Oui, ça me semble pas mal.

Reply

Marsh Posté le 15-01-2004 à 10:13:35    

LToPiQ[PPC] a écrit :

Je continue de penser que splitter ma table connexions en trois tables (une pour les anonymous où Session.SessionID est identifiant, l'autre pour les membres connectés où l'identifiant est UserID et l'autre pour les administrateurs où l'identifiant est AdminID) serait également une bonne chose.
Avec des tables plus légères, les risques de locks de plusieurs ressources en même temps est diminué.
 
Ensuite dans mon code, à chaque chargement de page :
 

Code :
  1. If AdminID <> "" then
  2.      Execute PS "admin"
  3. else
  4.      if UserID <> "" then
  5.           Execute PS "user"
  6.      else
  7.           Execute PS "anonymous"
  8. end if




 
je pense pas que ça pose vraiment problème. en effet, la table des connectés, même si elle sera intensivement solicitée, elle restera de toute petite taille (si t'as 1000 connections simultannées, c'est déjà que t'as beaucoup de visites, hors 1000 lignes c'est vraiment rien, à condition de pas faire des delete dedans dans tous les sens comme tu faisais avant ;))
 
pour les userID, ne pense que NULL si anonyme et le userId du gars dans un champ UserID reste mieu que plusieurs tables. après, c'est une question de goût et de besoins. si tu veux afficher "sur telle page, il y a X utilisateurs anonymes et les user suivants ..." ça sera plus facile dans une même table (un simple filtre sur la page). Mais si tu dis "y'a X mecs connus et Y anonymes sur le site", à ce moment, oui, tu peux faire x tables, ça sera limite plus simple à gérer.

Reply

Marsh Posté le 15-01-2004 à 10:15:07    

Bon merci pour tes conseils je vais mettre tout ça et je vais voir ce que ça donne :)

Reply

Marsh Posté le 15-01-2004 à 10:51:24    

j'espère que ça va résoudre ton problème :)
 
en tout cas, ça peut pas être pire que ton ancienne PS :D

Reply

Marsh Posté le 15-01-2004 à 10:53:09    

sinon au fait, ton ancienne PS... tu pouvais pas simplement virer la transaction ? parceque c'est pas des données vitales, si à un moment ça affiche portnawak quelques minutes parcequ'une requête a planté, ça devrait pas être bien gênant, si ?
 
parceque rien que ça, ça permettrait je pense de fortement diminuer le risque de collisions que tu avais.

Reply

Marsh Posté le 15-01-2004 à 11:24:58    

Aussi. Dans l'idéal je voulais être sûr de l'intégrité de mes données mais, en y réfléchissant, c'est pas vraiment aussi important que ça.

Reply

Marsh Posté le 15-01-2004 à 16:10:23    

Bon j'ai mis ce process en place sur le serveur de dév, mais malheureusement le système par sessionid est perfectible.
Durant mes tests (5 utilisateurs qui naviguent "agressivement" sur le site), j'ai eu deux entrées pour la même personne (même ip), avec des numéros de session différents.
Il n'y avait pas de raison spéciale, la personne en question n'a pas ouvert deux navigateurs...
donc je pense que je vais devoir oublier ça :/

Reply

Marsh Posté le 15-01-2004 à 16:13:16    

Reply

Marsh Posté le 15-01-2004 à 16:55:44    

select conn_ip, count(*) as nb_ip
from connections
group by conn_ip
order by nb_ip desc
 


80.14.163.3 - 3
195.93.66.8 - 3
81.250.246.20 - 2
195.93.73.17 - 2
195.93.73.8 - 1
198.81.26.8 - 1
209.111.109.122 - 1
212.23.162.37 - 1
213.228.40.248 - 1
213.36.132.213 - 1
213.36.186.117 - 1
213.44.213.65 - 1
217.167.39.237 - 1
24.203.169.159 - 1
62.210.119.23 - 1
65.215.117.2 - 1
66.130.45.138 - 1
66.74.37.90 - 1
68.101.105.33 - 1
69.34.192.124 - 1
80.13.80.111 - 1
81.250.86.212 - 1
81.251.35.68 - 1
81.49.137.175 - 1
81.49.250.235 - 1
81.50.179.38 - 1
81.50.2.103 - 1
81.50.88.75 - 1
81.51.123.234 - 1
81.51.72.244 - 1
81.53.50.63 - 1
81.62.5.66 - 1
81.80.169.225 - 1
82.64.159.65 - 1
80.15.135.219 - 1
80.15.142.99 - 1
80.15.201.111 - 1
80.15.86.227 - 1
80.9.122.2 - 1
81.167.112.9 - 1
81.250.207.185 - 1
195.93.73.10 - 1
129.215.13.83 - 1
193.201.0.1 - 1
193.253.197.20 - 1
193.253.222.180 - 1
194.3.130.178 - 1
194.51.44.116 - 1
195.93.64.14 - 1


 
Toutes les ip > 1 ça serait des gars qui ont soit ouvert plusieurs fenêtres soit passent par un routeur ? J'ai des doutes.


Message édité par LToPiQ[PPC] le 15-01-2004 à 16:58:00
Reply

Marsh Posté le 15-01-2004 à 17:21:45    

LToPiQ[PPC] a écrit :

Bon j'ai mis ce process en place sur le serveur de dév, mais malheureusement le système par sessionid est perfectible.
Durant mes tests (5 utilisateurs qui naviguent "agressivement" sur le site), j'ai eu deux entrées pour la même personne (même ip), avec des numéros de session différents.
Il n'y avait pas de raison spéciale, la personne en question n'a pas ouvert deux navigateurs...
donc je pense que je vais devoir oublier ça :/


c'est étrange ton truc... parceque le sessionId est identique tout au long de la session, s'il change, alors l'utilisateur perds toutes ses sessions... (et donc c'est normal qu'il soit vu comme une autre connection)

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed