SQL Server Blog

Hier werde ich einen SQL Blog aufbauen, der Sie mit Spezialthemen versorgt, die mich in meiner Arbeitszeit geärgert haben.

Zum Beispiel:

  • Deadlock-Monitoring
  • Indizierung

Vor allem möchte ich aber die Internas beleuchten, die für viele Analysen unerlässlich sind.

Mo

01

Sep

2014

Unterschied zwischen "TCP Port" und "TCP Dynamic Ports"

Wer bei einem SQL Server schon mal die Netzwerkeinstellungen angeschaut hat, hat feststellen können, dass es zwei verwirrend ähnliche Einstellungsfelder bei den TCP/IP-Einstellungen gibt:

  1. TCP Port
  2. TCP Dynamic Ports

Wenn man den SQL Server auf einen bestimmten TCP-Port festlegen will funktioniert das mit beiden.


ABER: Es gibt einige gravierende Unterschiede:

  • "TCP Port"
    • Kann nur einen Port enthalten
    • Wenn beim SQL Server Start der Port nicht exklusiv vom SQL Server genutzt werden kann, dann startet der SQL Server gar nicht
  • "TCP Dynamic Ports" 
    • Kann mehrere Ports enthalten
    • Wenn beim SQL Server Start der Port nicht exklusiv vom SQL Server genutzt werden kann, versucht der SQL Server einen der weiteren angegebenen Ports zu belegen. Wenn jeder der Ports in der Liste belegt sind, verwendet der SQL Server einen selbst gewählten freien Port.



Das heißt, man sollte sich genau überlegen, was man verwendet, damit es nicht zu Komplikationen kommt.

Beispielsweise:

  • Wenn der SQL Server automatisch periodisch neu gestartet werden soll und man möchte, dass der SQL Server auf jeden Fall gestartet ist, dann bietet sich an, die TCP Dynamic Ports zu setzen.
  • Wenn man jedoch Probleme mit einem geänderten Port haben könnte, auf Grund von Firewall-Freischaltungen, dann sollte man lieber TCP Port verwenden, um sofort zu merken, wenn nach dem Restart des SQL Servers der Port nicht belegt werden kann.


ABER: Es gibt einige gravierende Unterschiede:

  • "TCP Port"
    • Kann nur einen Port enthalten
    • Wenn beim SQL Server Start der Port nicht exklusiv vom SQL Server genutzt werden kann, dann startet der SQL Server gar nicht
  • "TCP Dynamic Ports" 
    • Kann mehrere Ports enthalten
    • Wenn beim SQL Server Start der Port nicht exklusiv vom SQL Server genutzt werden kann, versucht der SQL Server einen der weiteren angegebenen Ports zu belegen. Wenn jeder der Ports in der Liste belegt sind, verwendet der SQL Server einen selbst gewählten freien Port.

Das heißt, man sollte sich genau überlegen, was man verwendet, damit es nicht zu Komplikationen kommt.

Beispielsweise:

  • Wenn der SQL Server automatisch periodisch neu gestartet werden soll und man möchte, dass der SQL Server auf jeden Fall gestartet ist, dann bietet sich an, die TCP Dynamic Ports zu setzen.
  • Wenn man jedoch Probleme mit einem geänderten Port haben könnte, auf Grund von Firewall-Freischaltungen, dann sollte man lieber TCP Port verwenden, um sofort zu merken, wenn nach dem Restart des SQL Servers der Port nicht belegt werden kann.
0 Kommentare

Do

20

Mär

2014

RESOLVED: "Unable to determine if the owner ... of job has server access"-Error

Folgende Fehlermeldung erscheint, wenn zum Beispiel ein SQL Server Job einen Service Account aus dem Active Directory als Owner beinhaltet. Der Service Account steht jedoch in keiner Beziehung zum Service Account des SQL Servers oder des SQL Server Agents.

Unable to determine if the owner (<Domain>\<Username>) of job 41BE1A4A-1E6B-4CC2-946C-6D38C6612D0C has server access (reason: Could not obtain information about Windows NT group/user '<Domain>\<Username>', error code 0x5. [SQLSTATE 42000] (Error 15404)).

Der Fehler kommt daher, dass der SQL Server Service Account keine Berechtigung im Active Directory hat, um Informationen des "unbekannten" Benutzers aus dem Active Directory zu lesen.

LÖSUNG - Berechtigung im Active Directory

Active Directory: Security vom SQL Server Service Account
Active Directory: Security vom SQL Server Service Account

Um den Fehler nicht mehr zu erhalten, muss dem Service Account vom SQL Server das Recht gegeben werden, um die Informationen vom Service Account (der im SQL Job als Owner angegeben wurde) im Active Directory auszulesen.

 

Hierzu muss im Active Directory folgendes gemacht werden:

  1. Start -> Administrative Tasks -> Active Directory Users and Computers
  2. Finden Sie den Service Account des betroffenen SQL Servers
  3. Öffnen Sie mit Rechtsklick und Properties / Eigenschaften
  4. Wählen Sie den Reiter Security
  5. Fügen Sie den Service Account hinzu, der vom SQL Server ausgelesen werden soll und geben Sie Leserechte (Read) auf den Service Account

Um die Änderungen am SQL Server wirksam zu machen, muss der SQL Server Dienst neugestartet werden.

0 Kommentare

Do

29

Aug

2013

Konkurrierende Prozesse und Wait-Resources

Welcher Admin kennt das nicht: Die Anwender schreien, dass die Anwendung zu langsam ist? Was tun? Erstmal einen Blick auf die aktuellen Prozesse werfen. Welche Prozesse haben den Status "suspended"?

 

SELECT *
FROM   master..sysprocesses p
WHERE  p.status = 'suspended' 

 

Aber was soll diese Wait-Resource-Spalte mit der kryptischen Zeichenfolge? Diese Frage habe ich mir auch schon gestellt und aus diesem Grund eine Prozedur erstellt, die aus der kryptischen Zeichenfolge weitgehend den genauen Teil, der blockiert wird, spezifiziert.

Es gibt verschiedene Arten von waitresource-Strings. Ein wichtiges Kriterium für die Erkennung des Objektes, auf den gewartet wird, ist der vorangestellte Objekttyp. Dieser Typ kann folgende Ausprägungen haben:

RID: In diesem Fall liegt die Sperre auf der internen ROWID einer Tabelle. Eine ROWID existiert nur auf Tabellen, die keinen eindeutigen Clustered Index haben. Es ist eine vom SQL Server erzeugte GUID zur eindeutigen Identifikation des Datensatzes.

PAGE / PAG: Im Falle einer Page als Wartemedium handelt es sich um eine dedizierte 8-KB-Seite im SQL Server. Das können verschiedene Arten von Pages sein. Meistens handelt es sich um Index-Pages und Table-Pages.

EXT: EXT steht für Extent, welcher physikalisch mit einer Page vergleichbar ist, aber aus einer Menge von 8 Pages = 64 KB besteht. 

OBJECT / TAB: In diesem Fall wird auf das vollständige Objekt, meistens eine Tabelle, gewartet, das heißt, der Prozess möchte das Objekt vollständig sperren.

KEY: Wenn eine Tabelle kein Heap ist, sondern einen clustered Index hat, so wird analog zur RowID auf Zeilen von Primärschlüssel gewartet.

Mit diesen Erkenntnissen habe ich eine Prozedur erstellt, die aus diesem String, der die Wait-Resource identifiziert, das spezifische Objekt, also die Tabelle, und wenn möglich sogar die betroffenen Zeilen zurückgibt.

Bei dem Typ KEY ist es nämlich möglich durch den in Klammern stehenden Teil die betreffenden Zeilen im SQL Server zu identifizieren.

USE [ADMINDB]GOIF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ADMIN_Resolve_WaitResource]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[ADMIN_Resolve_WaitResource]GO

/*
 Autor: Patrick Fiedler
 Website: http://www.fiedler-sql-consulting.de
 Ablauf: Löst die WaitResourcen-Strings auf, wie zum Beispiel:
     OBJECT: 2:965199099:16
     RID: 6:1:2618395:15
     KEY: 7:72057594040811520 (4a98f6ce4bc0)
     PAGE: 5:1:13259434
     TAB: 4:149575571:0
*/
create procedure [dbo].[ADMIN_Resolve_WaitResource]
       @WaitResource nvarchar(100),
       @ResourceName nvarchar(100) = null output,
       @WithTableOutput bit = 0,
       @WithInvolvedRowsOutput bit = 1
as

set nocount on

declare @ResourceType nvarchar(50),
  @dbid int,
  @fileid int,
  @pageid bigint,
  @hobtid bigint,
  @ObjectId int,
  @Objectname nvarchar(200),
  @IndexId int,
  @LockRes nvarchar(50),
  @sql nvarchar(max)

set @ResourceType=substring(@WaitResource,1,CHARINDEX(':',@WaitResource)-1)
set @WaitResource=substring(@WaitResource,CHARINDEX(':',@WaitResource)+1,200)

create table #TempPage(
  ParentObject nvarchar(200),
  Object nvarchar(200),
  Field nvarchar(200),
  Value nvarchar(max)
)

if(@ResourceType in ('PAGE','PAG','EXT'))
begin
 -- Datenbank-ID trennen
 set @dbid=SUBSTRING(@WaitResource,1,CHARINDEX(':',@WaitResource)-1)
 -- FileID trennen
 set @fileid=SUBSTRING(@WaitResource,CHARINDEX(':',@WaitResource)+1,CHARINDEX(':',@WaitResource,CHARINDEX(':',@WaitResource)+1)-CHARINDEX(':',@WaitResource)-1)
 -- PageID trennen
 set @pageid=reverse(SUBSTRING(REVERSE(@WaitResource),1,charindex(':',reverse(@WaitResource))-1))
 
 set @sql=N'use ['+DB_NAME(@dbid)+']; DBCC PAGE (@dbid, @fileid, @pageid, 3) with tableresults'
 
 begin try
  insert into #TempPage
  exec sp_executesql @sql, N'@dbid int, @fileid int, @pageid bigint',@dbid=@dbid, @fileid=@fileid, @pageid=@pageid
 end try
 begin catch
  set @sql=N'use ['+DB_NAME(@dbid)+']; DBCC PAGE (@dbid, @fileid, @pageid) with tableresults'
  insert into #TempPage
  exec sp_executesql @sql, N'@dbid int, @fileid int, @pageid bigint',@dbid=@dbid, @fileid=@fileid, @pageid=@pageid
 end catch
 
 set @ObjectId=(select Value from #TempPage where Field='Metadata: ObjectId')
 set @IndexId=(select Value from #TempPage where Field='Metadata: IndexId')
 
 set @sql='   use ['+DB_NAME(@dbid)+'];    select @ResourceName=so.name+'' - ''+case when '+CAST(@IndexId as nvarchar)+'=0 then ''Heap'' else isnull(si.name,''N/A'') end,     @ObjectName=so.name   from sys.objects so    left join      sys.indexes si on so.object_id=si.object_id    where so.object_id='+CAST(@ObjectId as nvarchar)+'    and si.index_id='+CAST(@IndexId as nvarchar)
 exec sp_executesql @sql, N'@ResourceName nvarchar(100) output, @ObjectName nvarchar(200) output', @ResourceName=@ResourceName out, @ObjectName=@ObjectName out
 
 if(@WithTableOutput=1 and @WithInvolvedRowsOutput=1)
 begin
  set @LockRes=substring(@WaitResource, charindex('(',@WaitResource), 4000)
  
  set @sql='    use ['+db_name(@dbid)+'];    select Comment=''Involved Rows'',      InternalRowId= %%lockres%%,      o.*    from (     select t.Value     from #TempPage t     where t.Field in (''RowId'',''KeyHashValue'')    ) x    inner join      ['+@Objectname+'] o with (index='+cast(@IndexId as nvarchar)+') on x.Value= %%lockres%%    '
  exec(@sql)
  
 end
 
 if(@WithTableOutput=1)
 begin
  select WaitResource=@ResourceType+': '+@WaitResource,
    ObjectId=@ObjectId,
    IndexId=@IndexId,
    ResourceName=@ResourceName
 end
end else
if(@ResourceType='KEY')
begin
 -- Datenbank-ID trennen
 set @dbid=SUBSTRING(@WaitResource,1,CHARINDEX(':',@WaitResource)-1)
 -- FileID trennen
 if(charindex('(',@WaitResource)>0)
  set @hobtid=SUBSTRING(@WaitResource,CHARINDEX(':',@WaitResource)+1,CHARINDEX('(',@WaitResource,CHARINDEX(':',@WaitResource)+1)-CHARINDEX(':',@WaitResource)-1)
 else
  set @hobtid=SUBSTRING(@WaitResource,CHARINDEX(':',@WaitResource)+1,100)
 
 set @sql='   use ['+DB_NAME(@dbid)+'];   SELECT @ObjectId=o.object_id,     @Objectname=o.name,     @IndexId=i.index_id,     @ResourceName=o.name + '' - '' + isnull(i.name,''N/A'')   FROM sys.partitions p   INNER JOIN      sys.objects o ON p.OBJECT_ID = o.OBJECT_ID    LEFT JOIN      sys.indexes i ON p.OBJECT_ID = i.OBJECT_ID           AND p.index_id = i.index_id    WHERE p.hobt_id = '+cast(@hobtid as nvarchar)+'  '
 --print @sql
 exec sp_executesql @sql, N'@ObjectId bigint output, @Objectname nvarchar(200) output, @IndexId int output, @ResourceName nvarchar(100) output', @ObjectId=@ObjectId out, @Objectname=@Objectname out, @IndexId=@IndexId out, @ResourceName=@ResourceName out
 --print @ResourceName
 
 if(@WithTableOutput=1 and @WithInvolvedRowsOutput=1)
 begin
  set @LockRes=substring(@WaitResource, charindex('(',@WaitResource), 4000)
  
  set @sql='    use ['+db_name(@dbid)+'];    select Comment=''Involved Rows'',      InternalRowId= %%lockres%%,      *    from ['+@Objectname+'] with (index='+cast(@IndexId as nvarchar)+')    where %%lockres%% = '''+@LockRes+''''
  exec(@sql)
  
 end
 
 if(@WithTableOutput=1)
 begin
  select WaitResource=@ResourceType+': '+@WaitResource,
    ObjectId=@ObjectId,
    IndexId=@IndexId,
    ResourceName=@ResourceName
 end
end else
if(@ResourceType in ('OBJECT','TAB'))
begin
 -- Datenbank-ID trennen
 set @dbid=SUBSTRING(@WaitResource,1,CHARINDEX(':',@WaitResource)-1)
 -- ObjectID trennen
 set @ObjectId=SUBSTRING(@WaitResource,CHARINDEX(':',@WaitResource)+1,CHARINDEX(':',@WaitResource,CHARINDEX(':',@WaitResource)+1)-CHARINDEX(':',@WaitResource)-1)
 
 set @sql='use ['+DB_NAME(@dbid)+']; set @ResourceName=object_name('+CAST(@ObjectId as nvarchar)+')'
 exec sp_executesql @sql, N'@ResourceName nvarchar(100) output', @ResourceName=@ResourceName out
 
 if(@WithTableOutput=1)
 begin
  select WaitResource=@ResourceType+': '+@WaitResource,
    ObjectId=@ObjectId,
    IndexId=-1,
    ResourceName=@ResourceName
 end
end else
if(@ResourceType='RID')
begin
 -- Datenbank-ID trennen
 set @dbid=SUBSTRING(@WaitResource,1,CHARINDEX(':',@WaitResource)-1)
 --print @dbid
 -- FileID trennen
 set @fileid=SUBSTRING(@WaitResource,CHARINDEX(':',@WaitResource)+1,CHARINDEX(':',@WaitResource,CHARINDEX(':',@WaitResource)+1)-CHARINDEX(':',@WaitResource)-1)
 -- ObjectID trennen
 set @PageId=reverse(SUBSTRING(reverse(@WaitResource),CHARINDEX(':',reverse(@WaitResource))+1,CHARINDEX(':',reverse(@WaitResource),CHARINDEX(':',reverse(@WaitResource))+1)-CHARINDEX(':',reverse(@WaitResource))-1))
 --print @ObjectId
 
 set @sql=N'use ['+DB_NAME(@dbid)+']; DBCC PAGE (@dbid, @fileid, @pageid, 3) with tableresults'
 
 insert into #TempPage
 exec sp_executesql @sql, N'@dbid int, @fileid int, @pageid bigint',@dbid=@dbid, @fileid=@fileid, @pageid=@pageid
 
 set @ObjectId=(select Value from #TempPage where Field='Metadata: ObjectId')
 set @IndexId=(select Value from #TempPage where Field='Metadata: IndexId')
 
 set @sql='   use ['+DB_NAME(@dbid)+'];    select @ResourceName=so.name+'' - ''+case when '+CAST(@IndexId as nvarchar)+'=0 then ''Heap'' else isnull(si.name,''N/A'') end,     @Objectname=so.name   from sys.objects so    left join      sys.indexes si on so.object_id=si.object_id          and si.index_id='+CAST(@IndexId as nvarchar)+'   where so.object_id='+CAST(@ObjectId as nvarchar)+'    '
 exec sp_executesql @sql, N'@ResourceName nvarchar(100) output, @Objectname nvarchar(200) output', @ResourceName=@ResourceName out, @Objectname=@Objectname out
 
 if(@WithTableOutput=1 and @WithInvolvedRowsOutput=1)
 begin
  set @LockRes=substring(@WaitResource, charindex(':',@WaitResource)+1, 4000)
  set @sql='    use ['+db_name(@dbid)+'];    select Comment=''Involved Rows'',      InternalRowId= %%lockres%%,      *    from ['+@Objectname+']    where %%lockres%% = '''+@LockRes+''''
  exec(@sql)
  
 end
 
 if(@WithTableOutput=1)
 begin
  select WaitResource=@ResourceType+': '+@WaitResource,
    ObjectId=@ObjectId,
    IndexId=@IndexId,
    ResourceName=@ResourceName
 end
end

drop table #TempPage

--select @ResourceName
0 Kommentare

Di

04

Jun

2013

Microsoft SCOM: Setzen des MaintenanceMode für einzelne Computer

Ein Thema, das mich kürzlich beschäftigt hat, ist das programmatische Setzen des MaintenanceMode im Microsoft System Center Operations Manager (SCOM) eines einzelnen Rechners.

1. Der erste Versuch diese Funktionalität mit Powershell zu skripten, habe ich auf Grund des unverhältnismäßigen Zeitaufwands und der Komplexität auf Eis gelegt gehabt.

2. Im zweiten Schritt habe ich einfach beim Setzen des MaintenanceMode in der OperationsShell die Datenbankaktivitäten ausgewertet. Ergebnis war, dass es eine spezielle Prozedur gibt, die den MaintenanceMode starten kann. Die Prozedur heißt [p_MaintenanceModeStart] und enthält vorwiegend die Parameter, die auch in der Operations Shell abgefragt werden. Einzige Schwierigkeit ist die Ermittlung der richtigen "BaseManagedEntityId". Um die Handhabung des MaintenanceMode zu vereinfachen, habe ich nachfolgende Prozedur erstellt, die den übergebenen Computername in den MaintenanceMode setzt.

 

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ADMIN_SCOM_ComputerMaintenanceModeStart]'AND type in (N'P'N'PC'))
DROP PROCEDURE [dbo].[ADMIN_SCOM_ComputerMaintenanceModeStart]
GO

/*
    Autor:    Patrick Fiedler
    Ablauf:    1. Überprüfen, ob auf dem Server eine OperationsmanagerDatenbank existiert
            2. Sammeln der benötigten Informationen
            3. Setzen des MaintenanceMode
*/


create procedure [dbo].[ADMIN_SCOM_ComputerMaintenanceModeStart]
                            @ComputerName nvarchar(200),
                            @EndTime datetime,
                            @Comment nvarchar(200),
                            @StartTime datetime = null
as

set nocount on

declare @dbname nvarchar(250),
        @ScomDatabaseName nvarchar(250),
        @BaseManagedEntityId uniqueidentifier,
        @sql nvarchar(max)

-- Übersetzen der Datumswerte in UTC
set @EndTime = dateadd(minute, -datediff(mi,getutcdate(),getdate()), @EndTime)
if(@StartTime is not null)
    set @StartTime = dateadd(minute, -datediff(mi,getutcdate(),getdate()), @StartTime)

-- Check, ob ScomDB auf dem Server existiert
declare #cur cursor for
        select    d.name
        from    sys.databases d
        where    d.database_id>4

open #cur

fetch next from #cur into @dbname

while @@FETCH_STATUS=0
begin
    begin try
        exec('
        declare @Date datetime
        select top 1
                @Date=StartTime
        from    ['
+@dbname+'].dbo.MaintenanceMode m
        '
)
        
        set @ScomDatabaseName = @dbname
        print 'INFO: Die SCOM-Datenbank ist '+@ScomDatabaseName+'.'
        break
    end try
    begin catch
        print 'INFO: Die Datenbank '+@dbname+' ist keine SCOM-Datenbank.'
    end catch
    
    fetch next from #cur into @dbname
end

close #cur
deallocate #cur

-- Ermitteln der Entity-ID
set @sql='
SELECT    @BaseManagedEntityId=[BaseManagedEntityId]
FROM    ['
+@ScomDatabaseName+'].[dbo].[BaseManagedEntity]
where    BaseManagedEntityId=TopLevelHostEntityId
    and FullName like '
'Microsoft.Windows.Computer:%''
    and Name like '
''+@ComputerName+'%''
'

exec sp_executesql @sqlN'@BaseManagedEntityId uniqueidentifier output'@BaseManagedEntityId=@BaseManagedEntityId out

print 'BaseEntityId: '+cast(@BaseManagedEntityId as nvarchar(50))

if(@BaseManagedEntityId is null)
begin
    raiserror('ERROR: Es konnte keine BaseEntityId ermittelt werden.',16,1)
    return -1
end

set @sql='
use ['
+@ScomDatabaseName+']

declare @CurrentUser nvarchar(200)
set @CurrentUser = SUSER_NAME()

exec [p_MaintenanceModeStart]
            @BaseManagedEntityId=@BaseManagedEntityId,
            @ScheduledEndTime = @EndTime,
            @ReasonCode = 0, -- Unknown (Planned)
            @Comments = @Comment,
            @User = @CurrentUser, --
            @Recursive=1, -- all contained objects
            @StartTime=@StartTime -- now
'

exec sp_executesql    @sql
                    N'@BaseManagedEntityId uniqueidentifier, @EndTime datetime, @Comment nvarchar(200), @StartTime datetime'
                    @BaseManagedEntityId=@BaseManagedEntityId
                    @EndTime=@EndTime
                    @Comment=@Comment
                    @StartTime=@StartTime

0 Kommentare

Mi

22

Mai

2013

Unverständlicher Fehler beim Aufruf der xp_fileexist

Letztens hatte ich eine kuriose Fehlermeldung beim Ausführen von xp_fileexist.

 

DECLARE @FileExists INT, @MailAnhangPfad NVARCHAR(MAX)

SET @MailAnhangPfad = 'D:test.txt'  

EXEC master.sys.xp_fileexist @MailAnhangPfad, @FileExists OUT

 

Der Fehler sah folgendermaßen aus:

  

Msg 22027, Level 15, State 1, Line 0 

Usage: EXECUTE xp_fileexist [, OUTPUT]

 

Die Fehlermeldung wird eigentlich nur geworfen, wenn nichts übergeben wurde. Aber in diesem Fall kommt der Fehler auch, weil die Variable @MailAnhangPfad NVARCHAR(MAX) ist und das verarbeitet die Prozedur nicht. Deswegen hilft einfach statt NVARCHAR(MAX) NVARCHAR(4000) zu verwenden. Das heißt, der Code muss wie folgt geändert werden:

  

DECLARE @FileExists INT, @MailAnhangPfad NVARCHAR(4000)

SET @MailAnhangPfad = 'D:test.txt'  

EXEC master.sys.xp_fileexist @MailAnhangPfad, @FileExists OUT

 

Und dann klappt es auch ... 

0 Kommentare

Do

02

Mai

2013

ADMINDB vorweg - die SQL-Admin-Toolbox

Im Laufe der Zeit sammeln sich als SQL-Administrator bei den verschiedensten Notfällen bzw. Situationen Skripte an, die danach nicht mehr so einfach gefunden und wieder verwendet werden können. Aus diesem Grund ist in mir der Gedanke entstanden:

Wieso, wenn man so viele Skripte zur Lösung von Problemen erstellt, aber selten wieder verwendet werden kann, nicht eine separate SQL Server Datenbank erstellen, die eine Art Werkzeugkasten für den SQL Administrator darstellt und die erstellten Skripte wiederverwertbar in dieser Datenbank zu verewigen?

Wenn man jedoch einige Server mehr verwaltet, stellt sich die Frage, wie kann ich diese Datenbank auf allen Systemen aktuell halten. Man muss die Datenbanken immer aktuell halten, so dass man dafür eine Lösung finden muss:

  • Die möglichen Antworten sind einfach:
    • Entweder kann über die "Registered Server" (es sollten dazu aber alle zu aktualisierenden Server als Verbindung angelegt sein) der Code immer aktuell gehalten werden ... Rechtsklick auf die gesamte Servergruppe und "New Query" auswählen ... Die aktuell geänderte Prozedur in das Fenster kopieren und mit F5 ausführen und siehe da, die Prozedur ist auf allen Servern verteilt.
    • Oder man verwendet ein Powershell-Skript, dass ausgewählte Skripte und/oder ausgewählte Server bedient und auf den entsprechenden Servern bestimmte Skripte, also die jeweiligen CREATE bzw. ALTER-Skripte für Funktionen und Prozeduren ausführt.
    • Aber definitiv keine Lösung ist Backup-Restore dieser Datenbank, da sonst bei jeder kleinen Codeänderung ein neues Backup gemacht werden muss und im Nachgang jeder Server durch einen neuen Restore Daten bekommt.
0 Kommentare

Di

23

Apr

2013

Quellen für Performance-Optimierung und Objektsperren

Vor Kurzem erreichte mich eine Mail mit ein paar Fragen:

Frage:

Ich komme immer wieder in die Situation SQL Skripte und Reports zu entwickeln. In den durchaus komplexen und gewachsenen Strukturen unserer Kunden kommt es nun immer wieder auf Performance und sauberen Code (u.a. Zugriff auf Daten, Objektsperren, etc.) an. Daher die Frage ob Du gute Ressourcen (Blogs, Bücher, etc.) für folgende Themen empfehlen kannst?

 

  • Wie kann ein SQL Statement (z.B. Stored Procedure, SQL Script od. Report) hinsichtlich Performance überprüft werden? Insbesondere um zu erkennen, welche Codesektion geändert werden muss weil diese eine lange Abfragezeit verursacht?
  • Gibt es Best Practices für SQL Code in Bezug auf Datenzugriff? Hier stellt sich z.B. die Frage was passiert wenn ein SELECT auf eine Tabelle mit Millionen von Einträgen erfolgt und andere Prozesse auch darauf zugreifen?

Antwort:

So pauschal lässt sich das eben nicht sagen. Man kann zwar ein paar Grundregeln bzw. Grundvoraussetzungen benennen, aber der Rest hängt von der jeweiligen Situation ab.

Der Flaschenhals kann ja sehr unterschiedlicher Natur sein, zum Beispiel zu wenig I/O-Durchsatz, fehlende Indizes, erhöhte Komplexität der Abfrage, usw.

 

Wenn man nun aber vor hat, sich in Sachen Performance-Optimierung weiterzubilden, so ist es essentiell, dass man grundlegend die Funktionsweise des MS SQL Servers versteht und dass man nachvollziehen kann, wie der SQL Server seine Abfragen verarbeitet und versucht zu optimieren. Für diese Grundlagen empfehle ich jedem das Buch Microsoft SQL Server 2008 Internals (aktuell ist noch das 2008er Buch, aber am 31. Juli 2013 erscheint die 2012er Version des Buches). In dem Buch ist nicht beschrieben wie ich mir einen Wartungsplan zusammenklicke, aber dafür ist im Detail erklärt, wie der SQL Server agiert und wie er seine Daten organisiert. Mit diesem Verständnis von der Vorgehensweise des SQL Servers kann man wesentlich besser Performance-Engpässe in Abfragen erkennen.

 

Im nächsten Schritt ist es unerlässlich, zu lernen, wie man Ausführungspläne liest und dessen Inhalt deuten kann. Vor allem muss man identifizieren können, an welchen Stellen im Ausführungsplan Engpässe entstehen können.

 

Im Endeffekt kann man aber einige wichtige Regeln aufstellen, die in den meisten Fällen Anwendung finden können:

 

1. Lieber Komplexität verringern und mit Zwischenschritten arbeiten. Das macht die Arbeit für den Abfrageoptimierer einfacher und eindeutiger.

2. Immer Tests mit den Abfragen machen und die Ausführungspläne studieren.

3. Den SQL Server Profiler verwenden, um die Laufzeiten, die CPU-Auslastung und die I/O-Werte zu analysieren.

 

Zum Thema Datensperre gibt es im MS SQL Server 2008 einen eleganten Weg, dem with (nolock)-Hint aus dem Weg zu gehen, denn es gibt das Snapshot-Isolation-Level.

Ohne die Einstellung des Snapshot-Isolation-Level wird bei jedem SELECT während des Datenabrufes ein Shared-Lock (die Tabelle wird vor ungewollter Änderung während der Sperre geschützt) auf die involvierte Tabelle gesetzt und alle anderen Prozesse, die die Daten manipulieren wollen, sind gezwungen so lange zu warten, bis das SELECT vollzogen wurde.

 

Letztendlich benötigt man viel Erfahrung und Verständnis auf diesem Gebiet.

0 Kommentare