SQL vs Native – med et tvist af en indbygget SQL-bug?

Som regel kan en C5 på Native-databasen umiddelbart skiftes til SQL-databasen, hvis man ønsker det… Og det uden problemer af nogen art…

Det kræver naturligvis at især RecID-pegere er angivet korrekt i databasen med referencefelter til enten den korrekte tabels RecID-felt, eller til Common-tabellens RecID. Men det er mere for at kunne lave data-export/-import, så det er ikke specifikt for SQL.

I selve applikationskoden er der ingen problemer med den medfølgende applikation, MEN der kan naturligvis være ufordringer og muligheder for at optimere koden til at tage højde for at den nu afvikles på SQL.

Kort fortalt er det en rigtig god idé at sætte sig ind og bruge #USING-makroen (når man er ligeglad med resultatets sortering) og SQL-optimerings-makroerne, mere specifikt:

  • #SQLFieldList
  • #SQLSumList
  • #SQLCountList
  • #SQLMinList
  • #SQLMaxList

Du kan læse mere om disse i dette indlæg: SQL-kode på speed?

Fælles for kaldene er at de enten aflaster SQL-serveren (#USING), eller “skubber” mere afvikling ud til SQL-serveren så klienten “spares” for det. Og det giver hurtigere afvikling og bedre samtidighed (bedre oplevelse en række brugere får når de kører ting i C5 samtidigt).

Faktisk bør disse optimeringskald ALTID bruges – også hvis man udvikler til Native. På Native gør det blot ingenting og det fremtidssikrer din kode til SQL… Så i bund og grund er det en vane-sag…

I øvrigt bør man undgå konstruktioner i stil med:

INTRODUCE Notes[NotesIdx, &NotesFileId, &NotesRecId, &NotesLineNo]
SET Notes.Txt = 'TEST'
INSERT Notes

…dvs. altså at udnytte at opslags-syntaxen på INTRODUCE faktisk sætter de felter opslaget laves på, når man alligevel blot laver en INSERT.

For det første er det ikke så let at læse hvis man ikke er erfaren udvikler – og for det andet koster det faktisk både et opslag (SEARCH) i databasen og så naturligvis en INSERT. I stedet bør man ganske simpelt nøjes med INTRODUCE uden opslag og så selv sætte felterne:

INTRODUCE Notes
SET Notes.NotesFileId = &NotesFileId
SET Notes.NotesRecID = &NotesRecID
SET Notes.LineNumber = &NotesLineNo
SET Notes.Txt = 'TEST'
INSERT Notes

Det er naturligvis også det samme på både SQL og Native, om end en SEARCH på SQL er tungere (den skal jo typisk sendes over netværket fra klienten til serveren og svaret skal sendes retur).

NB: Microsoft har udgivet et ganske godt White Paper der gennemgår ovenstående – og flere ting. Det kan anbefales at læse dette hvis man udvikler mod C5 på SQL eller blot skriver kode som kan tænkes at havne på SQL en dag…

Men hvad så – er der fælder indbygget i SQL?
Generelt kan man sige at afvikling på SQL (modsat hvad mange tror) IKKE er hurtigere. Nærmere en smule langsommere… Til gengæld er samtidigheden og databasestabiliteten NOGET højere… Dvs. en gruppe af brugere vil opleve at de ikke låser (så længe) for hinanden og har man problemer med (typisk i store databaser: dvs. databaser over ca. 500MB) at databasefejl, nødvendig reindeksering eller at der fjernes TTS’er konstant – ja så vil man opleve markante forbedringer…

En meget værre fælde er der dog – hvis din tilpassede kode indeholder konstruktioner i stil med:

INTRODUCE SalesLine[RecID, &SLRecID]
IF SalesLine.RowNumber THEN
     ...
ENDIF

Umiddelbart skulle man jo tro at ovenstående ville fremfinde en SalesLine-post med RecID==&SLRecID og så kun afvikle det inden i IF-betingelsen hvis der fantes sådan en post… Og sådan virker det da også på Native.
MEN på SQL er RowNumber ALTID udfyldt i ovenstående…
Bug’en skyldes nok til dels at RowNumber og RecID altid er samme værdi på SQL (nemlig et fortløbende nummer på tværs af alle tabeller) – altså på nær i dette tilfælde, mens det i Native er to forskellige ting (hhv. fortløbende nummer indenfor samme tabel (RowNumber) og fortløbende nummer på tværs af alle tabeller (RecID)). Så kernerne ER faktisk forskellige på dette punkt – og kode der afvikles uden problemer på Native kan vise sig at afvikles forkert på SQL.
At ovenstående konstruktion fungerer på Native men ikke på SQL er farlig – for det er ulogisk.

Den korrekte konstruktion, der virker både på SQL og Native er i øvrigt at checke på RecID:

INTRODUCE SalesLine[RecID, &SLRecID]
IF SalesLine.RecID THEN
    ...
ENDIF

Vi har naturligvis haft fejlen forbi Microsoft, som dog kendte den i forvejen. Her betragter man dog fejlen som et designvalg og har derfor ikke umiddelbart planer om at rette den (man er formentligt bange for at der er kode i den virkelige verden der så vil gå i stykker fordi de udnytter opførslen på SQL).

Lad os slutte af med et lille optimeringstip: Har man meget tunge/langsomme kørsler – eller mulighed for det, så er det i øvrigt (modsat de fleste logisk SQL-anbefalinger) faktisk en RIGTIG god idé at placere SQL-serveren direkte på en terminalserver hvor man så afvikler C5 fra. Vi har set op mod 20% performanceboost (målt med C5s PerformanceTest.XAL), så det kan varmt anbefales hvis du har performance-/hastighedsproblemer…
Hastighedsforøgelsen får man i øvrigt formentligt ved at det er billigere for C5 at kommunikere direkte med en lokal SQL-server fremfor at pakke forespørgslen ned og sende den via netværket til serveren, der så skal pakke den ud, udføres den, pakke resultatet ned og sende det tilbage til C5. Egentligt ikke så mærkeligt når man tænker over det…