[VB & MS-SQL] Optimalisatie SQL

Pagina: 1
Acties:

  • Nazgul
  • Registratie: Februari 2000
  • Laatst online: 11-10-2022

Nazgul

Digital Pizza Crew

Topicstarter
Ik ben bezig met een Com component, wat straks vanuit een ASP pagina aangeroepen gaat worden, om data uit een geupload bestand in te lezen, te bewerken en te importeren in MS SQL Server.

Het geheel werkt prima, maar het importeren / verwerken / invoeren in de DB duurt 1 uur. :(

Nu weet ik zeker dat dat een stuk sneller moet kunnen, dus ben ik begonnen met de optimalisatie van het geheel. Na wat uitzoekwerk, blijkt de bulk van de tijd (70%) in het laatste gedeelte te zitten. (Dus het importeren in SQL server)

Bij leeswerk over optimalisatie kwam ik overal het verhaal van Stored Procedures tegen, dus heb ik mijn queries omgezet naar Stored Procedures. Dit leverde al 10 minuten tijdswinst op.

Hieronder heb ik een stukje van de gebruikte VB code (De totale applicatie bevat 5 van dit soort constructies, die met net iets andere data en tabellen werken), de Stored Procedure en het SQL script om de tabel te maken neergezet, want ik zit nog met een paar dingen.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Dim teller As Long
Dim raf As Integer
Dim com As New Command
Dim param As Parameter
Dim pbsArray

[Hier stond een heleboel code, die niet van belang is voor dit probleem]
    
Set com.ActiveConnection = conn
   
com.CommandText = "CRIS_PBS_Insert"
    
' Creeer alle parameters
Set param = com.CreateParameter("naam", adVarChar, adParamInput, 40, "")
com.Parameters.Append param
Set param = com.CreateParameter("rnr", adVarChar, adParamInput, 25, "")
com.Parameters.Append param
Set param = com.CreateParameter("tijd", adVarChar, adParamInput, 255, "")
com.Parameters.Append param


conn.BeginTrans
    
' Doorloop alle personen
For teller = LBound(pbsArray, 2) To UBound(pbsArray, 2)
    ' Set alle parameters
    com.Parameters("naam") = pbsArray(0, teller)
    com.Parameters("rnr") = pbsArray(3, teller)
    com.Parameters("tijd") = huidigeTijd
        
    ' Voer de persoon in, indien zijn RNR nog niet voorkomt.
    com.Execute raf, , adCmdStoredProc

Next
    
conn.CommitTrans

En het SQL Script (Door SQL Server gegenereerd, dus niet echt optimaal) Deze tabel bevat ongeveer 28000 records
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/****** Object:  Table [dbo].[personeel_gegevens]    Script Date: 10-10-01 13:40:43 ******/
CREATE TABLE [dbo].[personeel_gegevens] (
    [naam] [varchar] (40) NOT NULL ,
    [rnr] [varchar] (25) NOT NULL ,
    [pid] [varchar] (32) NOT NULL ,
    [tijd] [varchar] (255) NULL
)
GO

ALTER TABLE [dbo].[personeel_gegevens] WITH NOCHECK ADD 
    CONSTRAINT [PK_personeel_gegevens] PRIMARY KEY  CLUSTERED 
    (
        [pid]
    )  ON [PRIMARY] 
GO

 CREATE  UNIQUE  INDEX [CRIS_RNR] ON [dbo].[personeel_gegevens]([rnr]) ON [PRIMARY]
GO

GRANT  SELECT ,  INSERT ,  DELETE ,  UPDATE  ON [dbo].[personeel_gegevens]  TO [CRIS]
GO

En de gebruikte Stored Procedure
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE PROCEDURE CRIS_PBS_Insert (@naam varchar(40), @rnr varchar(25),  @tijd varchar(255)) AS

DECLARE @pid varchar(32)

SELECT @pid = Replace(Replace(Replace(newid(),'-',''),'}',''),'{','')

UPDATE personeel_gegevens
 SET naam=@naam, tijd=@tijd
 WHERE rnr=@rnr

IF @@ROWCOUNT<>1
  BEGIN
    INSERT INTO personeel_gegevens (pid, naam, rnr, tijd) 
     VALUES (@pid, @naam,  @rnr, @tijd) 
  END

Zoals gezegd, werkt het bovenstaande prima, maar omdat hij binnen een transactie in een lus door alle records loopt (Ongeveer 5000) creeert ie voor elk record een bijbehorende row-lock in de tabel. Wat de performance volgens mij niet ten goede komt.

Wat ik dus wil weten is het volgende:
- Is het beter om een exclusive table-lock te gebruiken, want ten tijde van de import kan de rest van de applicatie toch niet benaderd worden (Zo ja, hoe doe ik dit dan?)
- Zijn er nog andere dingen die ik kan doen om dit geheel te optimaliseren?

No trees were killed in the sending of this message. However a large number of electrons were terribly inconvenienced.


  • raptorix
  • Registratie: Februari 2000
  • Laatst online: 17-02-2022
Uh wel eens naar DTS gekeken?
Ik weet niet wat je import bestand is maar DTS kent vrijwel alle gangbare types aan.

Daarnaast kan je binnen dts ook gewoon scripten dan wel com objecten aanroepen.

  • Nazgul
  • Registratie: Februari 2000
  • Laatst online: 11-10-2022

Nazgul

Digital Pizza Crew

Topicstarter
Maar kan ik DTS ook vanuit een ASP pagina gebruiken?

No trees were killed in the sending of this message. However a large number of electrons were terribly inconvenienced.


  • raptorix
  • Registratie: Februari 2000
  • Laatst online: 17-02-2022
Je kan hem in principe starten vanuit dts dus moet mogelijk zijn.

  • Delphi32
  • Registratie: Juli 2001
  • Laatst online: 17-09 21:15

Delphi32

Heading for the gates of Eden

2 opmerkingen:
1. table lock voordat je alles gaat inserten is een correct idee, meteen doen
2. je primary key constraint pas achteraf zetten (en dan maar hopen dat het goed gaat) werkt ook erg goed. Nu moet jouw sp iedere keer alle voorgaande records controleren als hij een nieuw record insert, om te kijken of je primary key (een varchar veld, niet efficient) niet al bestaat. Als je ervan uit kan gaan dat je rnr in de te importeren dataset reeds uniek is, dan de unique constraint pas achteraf zetten.

  • 3volution
  • Registratie: September 2001
  • Niet online

3volution

tsja...

Moet het wel allemaal in 1 transactie? Dat kost toch ook wel het eea aan resources.

Verder zou ik iets gebruiken van
IF EXISTS(SELECT <id>)
UPDATE
ELSE
INSERT

Verwijderd

zet bovenin je stored proc:

SET NOCOUNT ON

Dit zorgt ervoor dat er geen '3 rows affected' terugkomt. Bij veel updates/inserts scheelt dit enorm.

wat verder erg kan schelen is het groter maken van je logfile. omdat je veel updates doet in 1 transaction, bewaart sqlserver alle logentries. SQLserver shrinkt en expand je logfile wellicht per update/insert, omdat je uit de logfile size loopt. Probeer die bv op 50MB te zetten als startwaarde. Vergroot ook de database file indien je tegen de maximum waarde aanzet. Zorg dat die files niet gefragmenteerd op je harddisk staan.

Verder: indexes (bv primary keys) op varchar/char/nvarchar etc fields levert veel overhead op. Beter is een numeric keyveld te introduceren, die als key te gebruiken. Indexes zijn dan stukken sneller.

Je gebruikt een newid() field maar stopt de waarde in een varchar. Daar is het niet voor bedoeld. Je kunt een field zelf RowGuid maken, en als type 'uniqueidentifier'. newid kost ook de nodige tijd.

Gebruik query analyzer om bottlenecks in je code op te sporen. Je gebruikt een stored procedure, je kunt dan kijken waar SQLserver de meeste tijd kwijt mee is, bv door die stored proc 25000 keer aan te roepen vanuit een andere stored proc en dan de executie te monitoren en te analyseren. Het beste lukt dat met SQL Profiler, die bij SQLserver zit.

ik denk, zo op de gok, dat de newid() call tezamen met de replace functies (?) erg traag zijn tov de rest, plus de index op de varchar key.

btw, die newid() call is het genereren van een COM CLSID. Dat kun je ook mbv een programma onder win32, dus die kun je voorberekenen in VB en wellicht al in je dataset klaarzetten.

Succes :)

Verwijderd

Kan je van relatie nr niet gewoon een autoincrement veld maken ipv een guid?

Verwijderd

Op donderdag 11 oktober 2001 01:12 schreef Delphi32 het volgende:
2 opmerkingen:
1. table lock voordat je alles gaat inserten is een correct idee, meteen doen
Ooit gehoord van multi-user? SQLserver zoekt zelf uit wat de beste locking is, en dat pakt 9 van de 10 keer goed uit.
2. je primary key constraint pas achteraf zetten (en dan maar hopen dat het goed gaat) werkt ook erg goed.
'hopen dat het goed gaat' en dan werkt het wel erg goed? hahah :D. 'hopen dat het goed gaat' is geen oplossing. WETEN dat het goed gaat, is essentieel, vandaar ook die transactie. Je gaat dan niet achteraf primary keys zetten. Wellicht runt hij deze update op een bestaande tabel... Databases is geen vak waarbij je even wat kunt knoeien. Datamodel ontwerpen -> tables designen -> tables implementeren -> keys definieren -> initiele indices en constraints definieren -> foreign keys definieren -> triggers definieren -> get/set stored procs definieren -> manipulate stored procs definieren -> higher level code implementeren.

'tis niet zo moeilijk.
Nu moet jouw sp iedere keer alle voorgaande records controleren als hij een nieuw record insert, om te kijken of je primary key (een varchar veld, niet efficient) niet al bestaat. Als je ervan uit kan gaan dat je rnr in de te importeren dataset reeds uniek is, dan de unique constraint pas achteraf zetten.
als je dat perse wilt doen, kun je ook zoiets gebruiken:
code:
1
2
3
4
5
6
7
8
9
10
11
12
BEGIN TRANSACTION transFoo
INSERT table (field1, field2) VALUES (value1, value2)
IF @@ERROR<>0
    GOTO Handler

-- more code
COMMIT TRANSACTION transFoo
RETURN

Handler:
     ROLLBACK TRANSACTION transFoo
     RETURN

(even illustratief voorbeeld, die begin trans etc zijn niet echt nodig).

Wat nodig is, is consistentie mbt WAT wil je doen? en waarom ga je dat dan doen? Als er een record wordt geinsert en er dan op basis van DAT record een nieuw record moet worden geinsert ergens anders, maak je een trigger die dat voor je doet. Die draait in de transaction van het insert statement, en je hebt nergens last van.

  • Nazgul
  • Registratie: Februari 2000
  • Laatst online: 11-10-2022

Nazgul

Digital Pizza Crew

Topicstarter
De database komt uit een bestaand systeem, dus ik kan niet even 1..2..3... een wijziging in het datamodel aanbrengen.
Er zijn door de oorspronkelijke ontwikkelaars een paar ontwerpbeslissingen genomen, waar ik het persoonlijk absoluut niet mee eens ben, maar waar ik nu eenmaal aan vast zit. :( (Zoals, bijvoorbeeld die varchar velden en die verkapte GUID)

Het hierboven gegeven tabelschema is trouwens een versimpelde versie van de echte. Daar zitten nog een x aantal kolommen bij voor adres, geslacht, ..... bij, maar om het bovenstaande overzichtelijk te houden, had ik die even uit de scriptjes geknipt. (Die andere velden zijn ook allemaal varchars. |:()

Maar een paar tips die ik zeker ga uitproberen zijn:
- SET NOCOUNT ON
- Table lock (Tijdens de import is het toch al onmogelijk voor andere gebruikers, om via de webapplicatie de database te benaderen. En aangezien de persoon die gerechtigd is voor de import ook de enige is die het beheer van de DB doet, is die kant ook 'dicht') (En tijdens de import heb ik nu ongeveer 5000 locks, table lock lijkt me dan wat sneller)
- Logfile vergroten
- Newid vanuit het COM object gaan regelen

Iedereen alvast bedankt voor de tips so far.

No trees were killed in the sending of this message. However a large number of electrons were terribly inconvenienced.


Verwijderd

Op donderdag 11 oktober 2001 10:42 schreef Nazgul het volgende:
- Table lock (Tijdens de import is het toch al onmogelijk voor andere gebruikers, om via de webapplicatie de database te benaderen. En aangezien de persoon die gerechtigd is voor de import ook de enige is die het beheer van de DB doet, is die kant ook 'dicht') (En tijdens de import heb ik nu ongeveer 5000 locks, table lock lijkt me dan wat sneller)
Volgens mij geeft SQLserver de locks weer vrij (row lock) zodra de update gelukt is. Het hangt van het serialization level af (of andere users op de table wijzigingen dynamisch zien of niet) of de wijzigingen worden doorgevoerd in andere resultsets. Dus het lijkt me sterk dat er 5000 rowlocks staan.

Wat je ook nog kunt proberen, is een 2e database gebruiken. Ikzelf doe dat bij een aantal sites die ik gebouwd heb waar veel data elke dag in geimporteerd wordt uit andere databases: de ene is actief, de andere niet en daar worden de nieuwe gegevens ingezet, is de update geslaagd, dan swap ik de databases om (dmv de connectionstring :)). Hierdoor is je site niet gelocked wanneer je de update draait en kun je alle truuks uithalen om het zo snel mogelijk te maken (table locks bv). Je actieve database kun je dan bv op slaan in een file of beter, in een klein databaseje ernaast waar je site specifieke gegevens in pleurt die niet wijzigen per import.
Pagina: 1