none
Tipps für weitere Analyse von Performanceunterschieden gesucht RRS feed

  • Frage

  • Hallo zusammen,

    wir haben hier ein Problem bei der Analyse von Performanceunterschieden, wo wir gerade nicht so recht weiterkommen und mal bisschen Unterstützung beim Brainstorming bräuchten.

    Ausgangssituation: Auf meinem Notebook laufen alle Abfragen schneller als auf nem dicken Server. Auf dem Server benötigen die Abfragen ca. die 1,5- bis 2-fache Ausführungszeit als auf dem Notebook.

    Das Notebook ist ein i5-7300U (2 Kerne, 4 logische Cores, 128k/512K/3M Cache,  https://ark.intel.com/de/products/97472/Intel-Core-i5-7300U-Processor-3M-Cache-up-to-3_50-GHz) mit 16GB RAM, SSD (SATA-III, 6GB/s).

    Der Server ist ein Xeon E3-1220 v6 (4 Kerne, 4 logische Cores, 256k/1M/8M Cache, https://ark.intel.com/products/97470/Intel-Xeon-Processor-E3-1220-v6-8M-Cache-3_00-GHz) mit 64GB RAM, SSDs (Connector weiss ich grad nicht, RAID 10, mit zusätzlichem Cache auf dem RAID-Controller).

    Beides also KabyLake-Architektur als Basis, also auch da keine massiven technologischen Unterschiede.

    Die IO-Systeme haben wir durchgemessen. Die Werte (Bandbreite und IOPS) sind ok, der Server hat ca. 30-40% mehr Durchsatz (Bandbreite als auch IOPS) ans das Notebook. Blockgröße der Datenträger ist auch ok und identisch.

    Wenn man alle obigen technischen Werte vergleicht sollte die Serverhardware also schon etwas flotter sein als das Notebook....

    Einziger Unterschied: Notebook: Windows 10. Server: Windows Server 2016.

    Sonstige Infos:

    Keine Virenscanner, Firewalls oder sonstige Systembremsen aktiv. Der Server wurde sogar komplett frisch aufgesetzt.
    Ausführung der Statements im SQL-Management-Studio jeweils lokal auf der Maschine.
    Beide Systeme mit SQL 2016 Developer Edition X64, gleicher Patchstand.
    Aktive Protokolle nur TCP/IP, Shared Memory und Named Pipes sind deaktiviert.
    Beide SQL-Server haben 8GB RAM zugewiesen. Auch sonst sind alle Konfigurationseinstellungen gleich.
    Auf beiden Servern läuft die gleiche DB mit identischem Datenbestand.
    Die DB hat ca. 1GB Größe.
    Indizies sind reorganisiert.
    Abfrageplan-Cache wurde jeweils nach Einhängen der DB geleert.
    Statistiken wurden jeweils nach Einhängen der DB aktualisiert.
    Abfragepläne für konkrete Beispielabfragen werden auf beiden Systemen quasi identisch erzeugt.
    Die Beispielabfragen sind auch recht simpel und bieten keine großen Möglichkeiten der Parallelisierung.
    Die Datenbestände für die Beispielabfragen sind relativ klein (ca. 100 Pages, paar hundert Datensätze), was zu einem guten Caching führt. Nach dem ersten Ausführen der Abfrage erfolgen keine weiteren physischen Reads, es wird nur noch logisch aus dem Speicher gelesen.

    Egal womit wir testen (simple oder komplexe Abfragen, kleine oder große Datenmengen, ...) der dicke Server ist immer langsamer. Und wir können uns einfach nicht erklären warum.

    Ideen? Wo könnte man noch suchen?

    Viele Grüße
    Uwe
    Donnerstag, 3. Mai 2018 10:09

Antworten

  • Nichts gegen Methodik, aber man muss ja nicht beim Urschleim anfangen, wenn es  um eine bereits bekannte Abfrage geht.

    Daher: Was heißt denn, der Plan ist "quasi identisch erzeugt"?  IST er identisch?
    Das kann man ja mal sicherstellen, indem man ihn speichert und vergleicht. (SSMS geht, Plan Explorer, kostenlos, ist besser: https://www.sentryone.com/plan-explorer)

    Was man ebenfalls sicherstellen kann, ist es, mit identischen Statistiken zu arbeiten. Aber wenn IOs identisch sein sollen, dann ist das eher nicht das Problem.

    Darum würde ich EINEN Schritt zurückgehen, (nicht gleich auf "Start"), und mir mal die Wait-Stats der Abfrage ansehen (Nicht des Servers, wie in dem Script vom Benjamin. Der liefert zu viel Hintergrundrauschen.)

    Diese bekommt man ebenfalls über den Plan Explorer, den ich für diese Analyse nur empfehlen kann. Das Tool liefert detaillierte Informationen zu allen verwendeten Ressourcen. Wenn die Frage also lautet, wie man vorgehen kann: das wäre mein Tipp.
    Alternativ kann man natürlich auch mit Extended Events tracen (Waits). Das ist erstmal meist ausreichend genau.


    Andreas Wolter (Blog | Twitter)
    MCSM: Microsoft Certified Solutions Master Data Platform/SQL Server 2012
    MCM SQL Server 2008
    MVP Data Platform MCSE Data Platform
    MCSM Charter Member, MCITP Charter Member etc.
    www.SarpedonQualityLab.com
    (Founder)

    Donnerstag, 3. Mai 2018 17:29
  • Hallo Uwe,

    ergänzend zu den Hinweisen von Andreas wäre auch interessant, die Wait Stats des Prozesses sowohl vom Server als auch vom Laptop zu vergleichen.

    Mögliche Engpässe (sofern die Abfragen wirklich identisch sind) wäre dann ja z. B. das Netzwerk, etc.

    Seit Microsoft SQL Server 2016 kannst Du die Waits einer bestimmten Session explizit auswerten. Dazu gibt es die Systemview sys.dm_exec_session_wait_stats.

    Lass doch mal Deine Abfrage und den zusätzlichen Befehl laufen und vergleiche die Ergebnisse. Zumindest sollten hier Unterschiede erkennbar sein:

    SELECT * FROM sys.dm_exec_session_wait_stats
    WHERE session_id = @@SPID;
    Könntest Du die beiden Ergebnisse hier mal posten. Ich vermute einfach mal, dass ASYNC_NETWORK_IO bei beiden Abfragen einen Unterschied zeigen (ist aber nur Raten zu diesem Zeitpunkt)


    Uwe Ricken (Blog | Twitter)
    Microsoft Certiied Master - SQL Server 2008
    Microsoft Certified Solution Master - CHARTER Data Platform
    Microsoft Certified Solution Expert - Data Platform
    db Berater GmbH
    Microsoft SQL Server Blog (german only)

    Freitag, 4. Mai 2018 07:45

Alle Antworten

  • Und was läuft sonst noch so auf dem Server im Hintergrund?

    Ist der Server virtualisiert, was auch Overhead bedeutet?

    Bei Virtualisierung ist die Frage: steht der Hostserver im Energymanagement auf höchste Leistung?
    Das klappt nämlich innerhalb der VM mit Automatik eher selten, so dass diese gebremst ist.

    Und was genau heißt hier Faktor 1,5 - 2?
    Handelt es sich da um Microsekunden;-)?

    Donnerstag, 3. Mai 2018 10:48
  • Sorry, vergessen: Der Server ist physisches Blech, nichts Virtuelles. Sonst läuft auf dem Server nichts, ist ein reiner SQL-Server. Ist ein extra erstelltes frisches Testsystem, nachdem uns das Verhalten bei einem Kundensystem aufgefallen war.

    Faktor 1,5-2 bedeutet je nach Umfang der Abfragen von doppelter Millisekunden- bis doppelter Sekunden-Zeit. Je nach Umfang der Abfragen. Jeweils CPU-Zeit, gemessen mit set statistics time on.

    Wie schon geschrieben sind die Abfragepläne auf beiden Maschinen weitestgehend identisch (minimale Unterschiede in den Schätzwerten der einzelnen Schritte).

    Wenn man sich mit set statistics io on die durchgeführten IO-Operationen anzeigen lässt, dann sind die auch auf beiden Maschinen jeweils identisch bei gleichen Abfragen.

    Ach ja, alles nur SELECT-Statements, keine Inserts/Updates, also keine Schreiboperationen.

    LG Uwe

    Donnerstag, 3. Mai 2018 11:12
  • Dadurch, dass nach der 1. Afrage ja im Wesentlichen alles im Hauptspeicher liegt, eine Parallelisierung der Abfrage quasi unnötig erscheint, wird ja alles in genau 1 CPU und aus dem Speicher befriedigt. Platte und IO liegen da nun mal komplett außen vor.

    Nun sollte man da mal einen reinen Speicher- und Kalkulationsbenchmark vergleichen.

    Ich habe da mal was gefiunden:

    http://cpu.userbenchmark.com/Compare/Intel-Xeon-E3-1220-V2-vs-Intel-Core-i5-7300U/m3482vsm223355

    Da stellt sich eben heraus, dass der I5 bei Einzelcore durchaus 11% schneller ist. Wahrscheinlich kommen dann noch Faktoren wie Memoryspeed, Bustakt u.v.m. zum tragen, neben dem Boost auf 3,5GHz statt 3,0.

    Der dicke Server kann seine Vorteile erst durch Parallelisierung bzw. die Bedienung vieler gleichzeitiger Abfragen zeigen. Mach da eher mal einen Benchmark mit 10, 20, 100 gleichzeitigen Abfragen.

    Und zu guter letzt: Abfrageoptimierung und Indizierung.
    Donnerstag, 3. Mai 2018 11:35
  • Dadurch, dass nach der 1. Afrage ja im Wesentlichen alles im Hauptspeicher liegt, eine Parallelisierung der Abfrage quasi unnötig erscheint, wird ja alles in genau 1 CPU und aus dem Speicher befriedigt. Platte und IO liegen da nun mal komplett außen vor.


    Soweit klar und ja auch schon oben indirekt beschrieben.

    Nun sollte man da mal einen reinen Speicher- und Kalkulationsbenchmark vergleichen.

    Ich habe da mal was gefiunden:

    http://cpu.userbenchmark.com/Compare/Intel-Xeon-E3-1220-V2-vs-Intel-Core-i5-7300U/m3482vsm223355

    Da stellt sich eben heraus, dass der I5 bei Einzelcore durchaus 11% schneller ist. Wahrscheinlich kommen dann noch Faktoren wie Memoryspeed, Bustakt u.v.m. zum tragen, neben dem Boost auf 3,5GHz statt 3,0.

    Du vergleichst mit dem v2, wir haben nen v6. Den gibts leider nicht in der Vergleichsliste, nur noch nen v3, und der ist bei der Single-Core-Performance fast identisch mit dem i5. Und bis 3,5GHz Boost gehen beide. Lt. https://browser.geekbench.com/processor-benchmarks liegen zwischen dem i5 und v6 ca. 50 Punkte Unterschied, das ist ca. 1%. Das sollte doch in der Praxis keine 50% Leistungsunterschied geben?

    Der dicke Server kann seine Vorteile erst durch Parallelisierung bzw. die Bedienung vieler gleichzeitiger Abfragen zeigen. Mach da eher mal einen Benchmark mit 10, 20, 100 gleichzeitigen Abfragen.

    Werde ich nochmal testen.

    Und zu guter letzt: Abfrageoptimierung und Indizierung.

    Das habe ich schon durch. Der Leistungsunterschied tritt ja auch bei simplen Sachen wie dem Lesen einer kompletten Tabelle mit ca. 200 Datensätzen genauso zutage. Und an nem simplen "SELECT * FROM Table ORDER BY PrimaryKey" ist jetzt nicht sooo viel Optimierungspotenzial vorhande.... :-(
    Donnerstag, 3. Mai 2018 12:10
  • Hallo,

    gehen wir doch mal methodisch an das Problem heran. Schaue dir doch mal bei beiden Systemen den Ausführungsplan an und überprüfe ihn mal auf Abweichungen, die muss es ja geben wenn die Laufzeit anders ist. Ggf. einfach mal beide Pläne hier posten.

    Dann führe doch mal bitte die folgenden Skripte aus und Poste die Ergebnisse ( Werte die hier nicht erscheinen sollen einfach weg machen)

    /*============================================================================
    	File:		001 - A00 - System Environment - Generic Information.sql
    
    	Summary:	Ths script returns default system information about the sql server
    				you wish to check.
    
    	Date:		May 2015
    	Session:	Analysis of a Microsoft SQL Server
    
    	SQL Server Version: 2008 / 2012 / 2014
    ------------------------------------------------------------------------------
    	Written by Uwe Ricken, db Berater GmbH
    
    	This script is intended only as a supplement to demos and lectures
    	given by Uwe Ricken.  
      
    	THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF 
    	ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED 
    	TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
    	PARTICULAR PURPOSE.
    ============================================================================*/
    
    -- General information about the sql server itself!
    SELECT	SERVERPROPERTY('Collation')						AS	[SQL Server Collation],
    		SERVERPROPERTY('Edition')					AS	[SQL Server Edition],
    		SERVERPROPERTY('ComputerNamePhysicalNetBIOS')			AS	[SQL Server MachineName],
    		SERVERPROPERTY('InstanceName')					AS	[SQL Server Instance],
    		SERVERPROPERTY('IsClustered')					AS	[SQL Server Clustered],
    		SERVERPROPERTY('IsHadrEnabled')					AS	[SQL Server AlwaysOn],
    		SERVERPROPERTY('HadrManagerStatus')				AS	[SQL Server AlwaysOn],
    		SERVERPROPERTY('ProductVersion')				AS	[SQL Server Version],
    		SERVERPROPERTY('ProductLevel')					AS	[SQL Server Version Level],
    		SERVERPROPERTY('IsIntegratedSecurityOnly')			AS	[SQL Server Authentifizierung];
    GO
    
    -- What traceflags are enabled in the sql server environment
    DBCC TRACESTATUS (-1);
    GO
    
    -- SQL Server Services information (SQL Server 2012 ONLY)
    SELECT	servicename								AS	[SQL Server Service],
    		startup_type_desc						AS	[Start Type],
    		status_desc								AS	[current status], 
    		last_startup_time						AS	[last startup],
    		service_account							AS	[Service Account],
    		[filename]								AS	[start command]
    FROM	sys.dm_server_services WITH (NOLOCK);
    GO
    
    -- Information about CPU and Memory
    SELECT	DOSI.cpu_count,
    		CAST(DOSI.physical_memory_kb / POWER(1024.0, 2)	AS NUMERIC(10, 2))	AS	PhysicalMem_GB,
    		CAST(DOSI.committed_kb / POWER(1024.0, 2) AS NUMERIC(10, 2))		AS	CommittedMem_GB,
    		CAST(DOSI.committed_target_kb / POWER(1024.0, 2) AS NUMERIC(10, 2))	AS	CommittedTargetMem_GB,
    		DOSI.max_workers_count
    FROM	sys.dm_os_sys_info AS DOSI;
    GO
    
    -- Are there any mdmp-files for further investigation?
    SELECT [filename], creation_time, size_in_bytes
    FROM sys.dm_server_memory_dumps WITH (NOLOCK) OPTION (RECOMPILE);
    /*============================================================================
    	File:		001 - A02 - System Environment - Configuration.sql
    
    	Summary:	Ths script returns the privileges of the system account
    				which will be used by the SQL Server Database Engine
    
    	Date:		May 2015
    	Session:	Analysis of a Microsoft SQL Server
    
    	SQL Server Version: 2008 / 2012 / 2014
    ------------------------------------------------------------------------------
    	Written by Uwe Ricken, db Berater GmbH
    
    	This script is intended only as a supplement to demos and lectures
    	given by Uwe Ricken.  
      
    	THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF 
    	ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED 
    	TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
    	PARTICULAR PURPOSE.
    ============================================================================*/
    USE master;
    GO
    
    SELECT	name,
    		description,
    		value_in_use,
    		is_dynamic,
    		C.is_advanced
    FROM	sys.configurations AS C
    WHERE	name IN
    (
    	N'recovery interval (min)',
    	N'locks',
    	N'fill factor (%)',
    	N'cross db ownership chaining',
    	N'max worker threads',
    	N'cost threshold for parallelism',
    	N'max degree of parallelism',
    	N'min server memory (MB)',
    	N'max server memory (MB)',
    	N'clr enabled',
    	N'optimize for ad hoc workloads',
    	N'Database Mail XPs',
    	N'xp_cmdshell'
    );
    GO
    /*============================================================================
    	File:		004 - A01 - Analysis of wait stats.sql
    
    	Summary:	This script is part of the collection from Glen Berry and shows
    				the wait stats of a system!
    				http://www.sqlskills.com/blogs/glenn/category/dmv-queries/
    
    	Info:		The original script has been modified by Uwe Ricken.
    				There is an additional column with a link to the library 
    				of prominent wait stats description to RED GATE library!
    
    	Date:		May 2015
    	Session:	Analysis of a Microsoft SQL Server
    
    	SQL Server Version: 2008 / 2012 / 2014
    ------------------------------------------------------------------------------
    	Written by Paul Randal, SQL Skills
    
    	This script is intended only as a supplement to demos and lectures
    	given by Uwe Ricken.  
      
    	THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF 
    	ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED 
    	TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
    	PARTICULAR PURPOSE.
    ============================================================================*/
    
    -- DBCC SQLPERF removes all measures from the system view.
    --DBCC SQLPERF('sys.dm_os_wait_stats', CLEAR);
    --GO
    WITH [Waits] AS
    (
    	SELECT	[wait_type],
    			[wait_time_ms] / 1000.0 AS [WaitS],
    			([wait_time_ms] - [signal_wait_time_ms]) / 1000.0 AS [ResourceS],
    			[signal_wait_time_ms] / 1000.0 AS [SignalS],
    			[waiting_tasks_count] AS [WaitCount],
    			100.0 * [wait_time_ms] / SUM ([wait_time_ms]) OVER() AS [Percentage],
    			ROW_NUMBER() OVER(ORDER BY [wait_time_ms] DESC) AS [RowNum]
        FROM	sys.dm_os_wait_stats
        WHERE	[wait_type] NOT IN
    	(
    		N'BROKER_EVENTHANDLER',         N'BROKER_RECEIVE_WAITFOR',
    		N'BROKER_TASK_STOP',            N'BROKER_TO_FLUSH',
    		N'BROKER_TRANSMITTER',          N'CHECKPOINT_QUEUE',
    		N'CHKPT',                       N'CLR_AUTO_EVENT',
    		N'CLR_MANUAL_EVENT',            N'CLR_SEMAPHORE',
    		N'DBMIRROR_DBM_EVENT',          N'DBMIRROR_EVENTS_QUEUE',
    		N'DBMIRROR_WORKER_QUEUE',       N'DBMIRRORING_CMD',
    		N'DIRTY_PAGE_POLL',             N'DISPATCHER_QUEUE_SEMAPHORE',
    		N'EXECSYNC',                    N'FSAGENT',
    		N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX',
    		N'HADR_CLUSAPI_CALL',           N'HADR_FILESTREAM_IOMGR_IOCOMPLETION',
    		N'HADR_LOGCAPTURE_WAIT',        N'HADR_NOTIFICATION_DEQUEUE',
    		N'HADR_TIMER_TASK',             N'HADR_WORK_QUEUE',
    		N'KSOURCE_WAKEUP',              N'LAZYWRITER_SLEEP',
    		N'LOGMGR_QUEUE',                N'ONDEMAND_TASK_QUEUE',
    		N'PWAIT_ALL_COMPONENTS_INITIALIZED',
    		N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP',
    		N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP',
    		N'REQUEST_FOR_DEADLOCK_SEARCH', N'RESOURCE_QUEUE',
    		N'SERVER_IDLE_CHECK',           N'SLEEP_BPOOL_FLUSH',
    		N'SLEEP_DBSTARTUP',             N'SLEEP_DCOMSTARTUP',
    		N'SLEEP_MASTERDBREADY',         N'SLEEP_MASTERMDREADY',
    		N'SLEEP_MASTERUPGRADED',        N'SLEEP_MSDBSTARTUP',
    		N'SLEEP_SYSTEMTASK',            N'SLEEP_TASK',
    		N'SLEEP_TEMPDBSTARTUP',         N'SNI_HTTP_ACCEPT',
    		N'SP_SERVER_DIAGNOSTICS_SLEEP', N'SQLTRACE_BUFFER_FLUSH',
    		N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
    		N'SQLTRACE_WAIT_ENTRIES',       N'WAIT_FOR_RESULTS',
    		N'WAITFOR',                     N'WAITFOR_TASKSHUTDOWN',
    		N'WAIT_XTP_HOST_WAIT',          N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG',
    		N'WAIT_XTP_CKPT_CLOSE',         N'XE_DISPATCHER_JOIN',
    		N'XE_DISPATCHER_WAIT',          N'XE_TIMER_EVENT')
    	AND	waiting_tasks_count > 0
    	)
    SELECT	MAX ([W1].[wait_type]) AS [WaitType],
    		CAST (MAX ([W1].[WaitS]) AS DECIMAL (16,2)) AS [Wait_S],
    		CAST (MAX ([W1].[ResourceS]) AS DECIMAL (16,2)) AS [Resource_S],
    		CAST (MAX ([W1].[SignalS]) AS DECIMAL (16,2)) AS [Signal_S],
    		MAX ([W1].[WaitCount]) AS [WaitCount],
    		CAST (MAX ([W1].[Percentage]) AS DECIMAL (5,2)) AS [Percentage],
    		CAST ((MAX ([W1].[WaitS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgWait_S],
    		CAST ((MAX ([W1].[ResourceS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgRes_S],
    		CAST ((MAX ([W1].[SignalS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgSig_S],
    		'http://documentation.red-gate.com/display/SM4/' + W1.wait_type AS [DocumentLink]
    FROM	[Waits] AS [W1] INNER JOIN [Waits] AS [W2]
    		ON ([W2].[RowNum] <= [W1].[RowNum])
    GROUP BY
    		[W1].[RowNum],
    		w1.wait_type
    HAVING	SUM ([W2].[Percentage]) - MAX ([W1].[Percentage]) < 100;
    GO



    Benjamin Hoch
    MCSE: Data Platform & Data Management and Analytics
    MCSA: SQL Server 2012/2014 & 2016 DB Administration
    MCSA: Windows Server 2012


    Donnerstag, 3. Mai 2018 12:22
  • Hallo Uwe,

    ergänzend:

    Was für ein Timinig wird beim Server bzw. Notebook angezeigt?

    -- Datenbank auswählen, Programmierbarkeit, Gespeicherte Prozeduren, Skript 'dbo.CPUSIMPLE' über Mausklick rechts, Gespeicherte Prozedur ausführen
    -- Timing = 10283 (ca. 10 Sek.) wären wünschenswert und entsprechen einem aktuellen System.
    
    USE [Datenbankname]
    GO
    /****** Object:  StoredProcedure [dbo].[CPUSIMPLE]    Script Date: 19.01.2015 08:43:52 ******/
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    CREATE PROCEDURE [dbo].[CPUSIMPLE]
    AS
     BEGIN
     DECLARE
     @n numeric(16,6) = 0,
     @a DATETIME,
     @b DATETIME
     DECLARE
     @f int
     SET @f = 1
     SET @a = CURRENT_TIMESTAMP
     WHILE @f <= 10000000
     BEGIN
     SET @n = @n % 999999 + sqrt(@f)
      SET @f = @f + 1
     END
     SET @b = CURRENT_TIMESTAMP
     PRINT 'Timing = ' + ISNULL(CAST(DATEDIFF(MS, @a, @b)AS VARCHAR),'')
     PRINT 'Res = ' + ISNULL(CAST(@n AS VARCHAR),'')
     END

    Hier findest Du weitere Diagnostic Queries:

    https://www.sqlskills.com/blogs/glenn/

    Schönen Abend.


    • Bearbeitet Joerg_x Donnerstag, 3. Mai 2018 17:26
    Donnerstag, 3. Mai 2018 17:22
  • Nichts gegen Methodik, aber man muss ja nicht beim Urschleim anfangen, wenn es  um eine bereits bekannte Abfrage geht.

    Daher: Was heißt denn, der Plan ist "quasi identisch erzeugt"?  IST er identisch?
    Das kann man ja mal sicherstellen, indem man ihn speichert und vergleicht. (SSMS geht, Plan Explorer, kostenlos, ist besser: https://www.sentryone.com/plan-explorer)

    Was man ebenfalls sicherstellen kann, ist es, mit identischen Statistiken zu arbeiten. Aber wenn IOs identisch sein sollen, dann ist das eher nicht das Problem.

    Darum würde ich EINEN Schritt zurückgehen, (nicht gleich auf "Start"), und mir mal die Wait-Stats der Abfrage ansehen (Nicht des Servers, wie in dem Script vom Benjamin. Der liefert zu viel Hintergrundrauschen.)

    Diese bekommt man ebenfalls über den Plan Explorer, den ich für diese Analyse nur empfehlen kann. Das Tool liefert detaillierte Informationen zu allen verwendeten Ressourcen. Wenn die Frage also lautet, wie man vorgehen kann: das wäre mein Tipp.
    Alternativ kann man natürlich auch mit Extended Events tracen (Waits). Das ist erstmal meist ausreichend genau.


    Andreas Wolter (Blog | Twitter)
    MCSM: Microsoft Certified Solutions Master Data Platform/SQL Server 2012
    MCM SQL Server 2008
    MVP Data Platform MCSE Data Platform
    MCSM Charter Member, MCITP Charter Member etc.
    www.SarpedonQualityLab.com
    (Founder)

    Donnerstag, 3. Mai 2018 17:29
  • Hallo Uwe,

    ergänzend zu den Hinweisen von Andreas wäre auch interessant, die Wait Stats des Prozesses sowohl vom Server als auch vom Laptop zu vergleichen.

    Mögliche Engpässe (sofern die Abfragen wirklich identisch sind) wäre dann ja z. B. das Netzwerk, etc.

    Seit Microsoft SQL Server 2016 kannst Du die Waits einer bestimmten Session explizit auswerten. Dazu gibt es die Systemview sys.dm_exec_session_wait_stats.

    Lass doch mal Deine Abfrage und den zusätzlichen Befehl laufen und vergleiche die Ergebnisse. Zumindest sollten hier Unterschiede erkennbar sein:

    SELECT * FROM sys.dm_exec_session_wait_stats
    WHERE session_id = @@SPID;
    Könntest Du die beiden Ergebnisse hier mal posten. Ich vermute einfach mal, dass ASYNC_NETWORK_IO bei beiden Abfragen einen Unterschied zeigen (ist aber nur Raten zu diesem Zeitpunkt)


    Uwe Ricken (Blog | Twitter)
    Microsoft Certiied Master - SQL Server 2008
    Microsoft Certified Solution Master - CHARTER Data Platform
    Microsoft Certified Solution Expert - Data Platform
    db Berater GmbH
    Microsoft SQL Server Blog (german only)

    Freitag, 4. Mai 2018 07:45
  • "Ausführung der Statements im SQL-Management-Studio jeweils lokal auf der Maschine."

    Da sollte der Serverzugriff so intelligent sein, keine Netzwerkverbindung aufbauen zu müssen.

    Vergleiche doch mal spaßeshalber deine SQLs als

    select count(*) from
    (select .... ) x
    where 1=1

    Somit fällt die Übertragunsgzeit zwischen Server und Client (SQLStudio) i.W. weg, da als Ergebnis nur 1 Satz kommt.
    Der "where 1=1" ist u.U. erforderlich, da es ggf. eine Count(*)-Optimierung gibt, wenn keine weitere Whereklausel vorhanden ist.
    Somit beschränkt sich das Messergebnis dann eher auf die reine SQL-Serveraktivität.

    Desweiteren sollte ein Test auch so gestaltet sein, dass Server und Client nicht auf derselben Maschine laufen um alle Störeinflüsse des Clients auszuschließen.
    Also mit einem Client wahlweise auf das Notebook/den Server zugreifen und messen.

    Ansonsten kann man z.B. per Sysinternals Processexplorer die CPU-Affinity (ggf. je Thread) prüfen, um sicherzustellen, dass Server und Client auf unterschiedlichen CPU's laufen.

    Dazu ist ja noch folgendes zu berücksichtigen, dass ein Select nicht immer erst intern in eine temporäre Tabelle abgelegt und dann übertragen wird sondern während des Abholens der Daten vom Client berechnet wird.
    Dies hat machmal auch den Effekt, dass während des Abholens verschieden lange Pausen entstehen, da der SQL-Server den nächsten Block an Daten erst berechnen muss und andererseits den Vorteil, dass bei Ausbleiben weiterer Lesevorgänge auch keine weiteren Berechnungen erfolgen.
    Bestes Beispiel sind Abfragen aus dem Studio wenn die Anzahl Sätze des Ergebnisses z.B. auf die esten 100 eingeschränkt wird obwohl noch viele 1000de Daten mehr vorhanden wären.

    Der obige Select schließt diese Verfahren komplett aus.

    Freitag, 4. Mai 2018 08:13
  • ...

    ergänzend zu den Hinweisen von Andreas wäre auch interessant, die Wait Stats des Prozesses sowohl vom Server als auch vom Laptop zu vergleichen.

    ...

    das ist doch eigentlich genau das, was ich meinte, oder?

    Die wahre Ergänzung wäre dann eher der Hinweis auf die sys.dm_exec_session_wait_stats.

    - immer mit dem Hinweis, dass diese nicht die ganze Wahrheit sagt, aber erstmal oft ausreichend genau ist.


    Andreas Wolter (Blog | Twitter)
    MCSM: Microsoft Certified Solutions Master Data Platform/SQL Server 2012
    MCM SQL Server 2008
    MVP Data Platform MCSE Data Platform
    MCSM Charter Member, MCITP Charter Member etc.
    www.SarpedonQualityLab.com
    (Founder)

    Freitag, 4. Mai 2018 08:36
  • ..

    select count(*) from
    (select .... ) x
    where 1=1

    Somit fällt die Übertragunsgzeit zwischen Server und Client (SQLStudio) i.W. weg, da als Ergebnis nur 1 Satz kommt.
    Der "where 1=1" ist u.U. erforderlich, da es ggf. eine Count(*)-Optimierung gibt, wenn keine weitere Whereklausel vorhanden ist.
    Somit beschränkt sich das Messergebnis dann eher auf die reine SQL-Serveraktivität.

    ...

    ein WHERE 1 = 1 ist nur aus Code-Verwaltungstechnischen Gründen manchmal hilfreich und interessiert den SQL Server überhaupt nicht. Das "optimiert er 100% weg". ;-)

    - deswegen sieht man davon auch im Plan dann: NICHTS. Oder ist Dir das noch nicht aufgefallen?

    - wer's genau wissen will: das geschieht in der Phase "Constant Folding" des Optimizers.


    Andreas Wolter (Blog | Twitter)
    MCSM: Microsoft Certified Solutions Master Data Platform/SQL Server 2012
    MCM SQL Server 2008
    MVP Data Platform MCSE Data Platform
    MCSM Charter Member, MCITP Charter Member etc.
    www.SarpedonQualityLab.com
    (Founder)

    Freitag, 4. Mai 2018 08:49
  • Dann tausche das where 1=1 doch gegen eine nicht optimierbare Klausel aus;-), z.B. where IrgendEinFeld >= <MinVal>. Da fällt einem bestimmt was dazu ein.
    Ich will doch nur erreichen, dass Aspekte des Clients vollkommen außen vor bleiben. Nur dann ist die reine Leistung tatsächlich ermttelbar.

    Freitag, 4. Mai 2018 09:38
  • Dann tausche das where 1=1 doch gegen eine nicht optimierbare Klausel aus;-), z.B. where IrgendEinFeld >= <MinVal>. Da fällt einem bestimmt was dazu ein.
    Ich will doch nur erreichen, dass Aspekte des Clients vollkommen außen vor bleiben. Nur dann ist die reine Leistung tatsächlich ermttelbar.

    Ist doch völlig ok, dass Du das möchtest.

    Spar Dir doch die Where-Bedingung einfach ganz. ;-) Die hat keinen Effekt auf die Messvergleichbarkeit.


    Andreas Wolter (Blog | Twitter)
    MCSM: Microsoft Certified Solutions Master Data Platform/SQL Server 2012
    MCM SQL Server 2008
    MVP Data Platform MCSE Data Platform
    MCSM Charter Member, MCITP Charter Member etc.
    www.SarpedonQualityLab.com
    (Founder)

    Freitag, 4. Mai 2018 09:44
  • Dies würde bedeuten, dass Count(*) nicht optimiert würde (wie 1=1).
    Von anderen DB's kenne ich das durchau, dass ohne Wherebedingung ein Count(*) direkt die Anzahl der Zeilen der Tabelle liefert, da diese ja bekannt sind.

    Und je nach Optimizerfähigkeit (die ja auch ständig erweitert wird) kann der auch schon mal in die derived Table reinschauen.

    Haben nach obiger Beschreibung die Selects bereits eine Where-Klausel, so kann ein Abschlusswhere natürlich entfallen.

    Freitag, 4. Mai 2018 10:47
  • Dies würde bedeuten, dass Count(*) nicht optimiert würde (wie 1=1).
    Von anderen DB's kenne ich das durchau, dass ohne Wherebedingung ein Count(*) direkt die Anzahl der Zeilen der Tabelle liefert, da diese ja bekannt sind.

    ...

    Ob und wann ein count optimiert wird, wird genauso gehandhabt wie bei allen anderen Abfragen. Außer bei Columnstore Indexen gibt es da keine spezielle Technik.

    Ein Satz wie "die Anzahl der Datensätze ist ja bekannt", ist extrem untechnisch und einfach Käse.

    Kein DBMS der Welt "kennt" oder "weiß" irgendetwas. Informationen werden gespeichert. Und zwar ist fest definierter Form und Stelle. Worauf Du hier hinauswillst, weiß ich nicht. Aber wie gesagt, da gibt es kein "internes Wissen". - Nebenbei gesagt ändert sich eine Anzahl auch gern mal.

    Also, als Tipp, weniger mit Vermutungen arbeiten, als mit klar bekannten Fakten. Dann kann man sich vieles logisch herleiten.


    Andreas Wolter (Blog | Twitter)
    MCSM: Microsoft Certified Solutions Master Data Platform/SQL Server 2012
    MCM SQL Server 2008
    MVP Data Platform MCSE Data Platform
    MCSM Charter Member, MCITP Charter Member etc.
    www.SarpedonQualityLab.com
    (Founder)

    Freitag, 4. Mai 2018 11:38
  • Hallo zusammen,

    erst mal danke für alle Antworten. Die ganzen Hinweise werde ich mir für zukünftige Probleme zur Seite packen und mal in Ruhe anschauen.

    So wie es aktuell aussieht, ist das grundsätzliche Problem behoben. Der Server rennt jetzt wie es erwartungsgemäß sein sollte. Performance und Antwortzeiten passen jetzt.

    Die ganz genaue Ursache prüfen wir gerade noch, es hat aber auf alle Fälle was mit BIOS-Einstellungen zu tun. Oder das BIOS bzw. dessen Defaulteinstellungen waren irgendwo kaputt. Wahrscheinlich Letzteres, zumindest deuten alle bisherigen Vergleichstests mit wechselnden Einstellungen darauf hin. Aber wie gesagt, die Analysen laufen noch.

    Nochmals danke für die Hilfe und allseits ein schönes Wochenende!

    LG Uwe

    Freitag, 4. Mai 2018 12:00
  • "Kein DBMS der Welt "kennt" oder "weiß" irgendetwas."

    Das kann ich leider so nicht stehen lassen. Die DB2/400 ist da schon sehr weit und zum Teil auch aus der Historie.
    Ein einfacher "Select count(*) From Table" kommt sofort zurück. Die Anzahl der aktiven Sätze ist fix gespeichert.

    Außerdem kann man auch Indexstatistiken bemühen:

    http://www.i-programmer.info/programming/database/6576-sql-server-quickly-get-row-counts-for-tables-heaps-indexes-and-partitions.html

    Die Anzahl der Sätze ergibt sich im Umkehrschluss aus der Anzahl eindeutiger Schlüssel eines Indexes.
    Irgendwo muss der Optimizer ja seine Informationen herhaben um sich für irgendeinen Index zu einscheiden.

    Außerdem ist dieser Wert temporär sogar genauer, da während eines Tablescans sich die Satzzahl ja wieder verändern kann.
    Bei der Firebird wird die Indexselektivity mit dem Wert "1/Anzahl Keys" festgelegt. Bei einem Unique-Key, und wer arbeitet schon noch ohne Identity-Column;-), ist also wieder um der Kehrwert die Anzahl Sätze.

    "weniger mit Vermutungen arbeiten" beherzige ich gerne, wenn ich nur Vermutungenen habe;-).

    Und wer sagt uns denn nicht, dass die Entwickler diesen Weg für einen Count(*)-Optimierung nicht irgendwann mal wählen?

    Freitag, 4. Mai 2018 12:12
  • "Kein DBMS der Welt "kennt" oder "weiß" irgendetwas."

    Das kann ich leider so nicht stehen lassen. Die DB2/400 ist da schon sehr weit und zum Teil auch aus der Historie.
    Ein einfacher "Select count(*) From Table" kommt sofort zurück. Die Anzahl der aktiven Sätze ist fix gespeichert.

    Außerdem kann man auch Indexstatistiken bemühen:

    ...

    Irgendwo muss der Optimizer ja seine Informationen herhaben um sich für irgendeinen Index zu einscheiden.

    Entschuldige, aber was soll das jetzt? Zitiere mich bitte vollständig und verändere nicht meine Aussage:

    ich schrieb nämlich: " Informationen werden gespeichert. Und zwar ist fest definierter Form und Stelle. "

    Und genau so ist es nunmal. Im nächsten Abschnitt gibst Du mir sogar recht.
    Einfach etwas genauer arbeiten.

    Ich bin sicher, das würde auch Deine Kunden freuen.

    Was irgendwann mal erfunden wird, ein Speicherloses Memory, ist Zukunft (und irgendwie auch jenseits meiner Vorstellungskraft.). Davon reden wir nicht.




    Freitag, 4. Mai 2018 12:18
  • "Ich bin sicher, das würde auch Deine Kunden freuen."
    Die freut es schon seit mehr als 20 Jahren;-), daher kann mich so eine Vermutung nicht treffen.

    " Informationen werden gespeichert. Und zwar ist fest definierter Form und Stelle. "
    Also weiß ein DBMS doch irgendwas.

    "Ein Satz wie "die Anzahl der Datensätze ist ja bekannt", ist extrem untechnisch und einfach Käse."

    Ok, dann spezifiziere ich das:

    Die Anzahl der Datensätze einer Tabelle ist zu einem bestimmten Zeitpunkt bekannt, nämlich in der Indexstatistik.
    Egal auf welchem Wege diese abfragbar ist.

    Un nun ist gut.

    Freitag, 4. Mai 2018 12:33
  • Hallo Baldur,

    ich hatte mich - aber nur kurzfristig - ebenfalls mit dieser Art der Ermittlung von COUNT beschäftigt, da ich es charmant fand, nicht mehr vollständig durch einen Index laufen zu müssen.

    Der von Dir genannte Artikel verschweigt jedoch einen eklatanten Nachteil dieser Methode - sie ist nicht konsistent, da sie DIRTY READS verwendet. Wenn man also auf eine exakte Anzahl von Daten angewiesen ist, halte ich diesen Vorschlag für eher kontraproduktiv.

    Mit dem folgenden Beispiel kann man das sehr gut reproduzieren:

    -- Erstellen einer Testtabelle
    SELECT	*
    INTO	dbo.messages
    FROM	sys.messages
    WHERE	1 = 0;
    GO
    
    -- Ermitteln der aktuellen Anzahl von Datensätzen (0)
    SELECT * FROM sys.partitions
    WHERE	OBJECT_ID = OBJECT_ID(N'dbo.messages');
    GO
    
    -- Eintragen von 22 Datensätzen in einer expliziten Transaktion
    BEGIN TRANSACTION;
    GO
    	INSERT INTO dbo.messages
    	SELECT * FROM sys.messages
    	WHERE	severity = 12;
    	GO

    Die erste Abfrage - vor Beginn der Transaktion - zeigt den korrekten Wert von 0 Datensätzen an. Beginnst Du nun die Transaktion, versuche die Abfrage erneut in einer zweiten Session!

    -- in einer 2. Session bitte den folgenden Code ausführen!
    SELECT * FROM sys.partitions
    WHERE	OBJECT_ID = OBJECT_ID(N'dbo.messages');
    GO

    Obwohl die Transaktion noch nicht abgeschlossen ist, werden die 22 Datensätze bereits in sys.partitions angezeigt. Der Grund hierfür (aber das ist jetzt wirklich nur Spekulation!) liegt in der View selbst. Den "wichtigen" Teil von sys.partitions habe ich mal extrahiert hier dargestellt:

    	-- Get rowstore partitions
    	SELECT 
    		partition_id,
    		object_id,
    		index_id,
    		partition_number,
    		hobt_id,
    		isnull(ct.rows, p.rows) AS rows,
    		filestream_filegroup_id,
    		data_compression,
    		data_compression_desc	
    		from partitions_rowstore p OUTER APPLY OpenRowset(TABLE ALUCOUNT, p.partition_id, 0, 0, p.object_id) ct

    Wohlgemerkt - es ist nur Spekulation! Wie Du erkennen kannst, verwendet Microsoft SQL Server intern eine Systemressource ALUCOUNT (hierbei handelt es sich um eine Table Valued Function!). Die Ausgabe verweist zunächst auf einen möglichen Wert dieser Funktion und erst dann vom Ergebnis der CTE [partitions_rowstore]!

    Aus meiner Sicht gibt es nichts dagegen einzuwenden, diese Methode zu verwenden, wenn das Ergebnis nicht akkurat sein muss - ansonsten ist hier höchste Vorsicht geboten.

    Ein weiterer Grund für die Ablehnung ist aus meiner Sicht der Aspekt der Sicherheit. Damit ein Benutzer ein Ergebnis aus sys.partitions erhält, muss er SELECT-Berechtigungen auf der Tabelle erhalten. Ansonsten werden keine Informationen zum Objekt angezeigt! Eine Besitzverkettung mit Hilfe einer View funktioniert ebenfalls nicht - somit erübrigt sich die weitere "Usability" dieser Methode.

    Herzliche Grüße von der Ostsee...

    PS: Obwohl ich diese Diskussion sehr interessant finde, sei mir dennoch die Frage erlaubt, inwieweit das noch mit dem eigentlichen Thema zu tun hat :)


    Uwe Ricken (Blog | Twitter)
    Microsoft Certiied Master - SQL Server 2008
    Microsoft Certified Solution Master - CHARTER Data Platform
    Microsoft Certified Solution Expert - Data Platform
    db Berater GmbH
    Microsoft SQL Server Blog (german only)


    Freitag, 4. Mai 2018 14:00
  • Nö damit eigentlich nichts, es ging ja nur um den Tipp, den SQL messtechnisch rein auf den Server zu beschränken.

    Und was die Genauigkeit eines Counts angeht, so bin ich da gerade bei großen Tabellen auf den Typ der Transaktion angewiesen. Bei normalem ReadCommited ist der Tablesan halt auch irgendwann mal an gerade gelöschten Sätzen anderer Transaktion längst vorbei. Verlassen kann man sich da nicht so.
    Da ist ggf. ALUCOUNT die besserer Alternative (für Komplettabfrage), da in 99,9% aller Fälle eine Transaktion committed wird und somit eben die Wahrscheinlichkeit hier höher ist als beim Tablescan.

    Noch schlimmer wird es mit Datenbanken und aktiven Satzversionen. Da kann es durchaus schon mal vorkommen, dass die Werte gar nicht stimmen können durch viele offenen Transaktionen.
    Die DB2/400 unterstützt bisher keine Satzversionen. Und was macht die, wenn so ein BI-Tool daherkommt und die Tabelle per Snapshot-Transaktion liest?
    Korrekt, die Tabelle wird (leider) gegen Veränderung komplett gesperrt und blockt stundenlang wegen Millionen Datensätzen das gesamte ERP-System. Mache ich das beim SQL-Server macht der mal eben die Satzversion scharf ohne das es einer merkt. Die Performance geht runter und durch die ewige lange Trannsaktion auch hier von Millionen Sätzen platzte im reichlich aktiven System die Tempdb.
    Und selbst wenn es gut geht, ist der SQL-Server noch stunden mit Aufräumen beschäftigt.

    Und dann tauchen da halt immer wieder so Fragen auf, warum ist die Performance für dieses oder jenes einfach grottenschlecht.
    PS: Da hatte ich in der Vergangenheit mal das Problem, dass das Transaktionslog 20GB bei einer 300MB-Datenbank groß war, die Performance unter aller ..., und kein Admin weit und breit. Nach der Bereinigung ging alles wieder flott.
    Fazit: es gibt viele Stellschrauben und auch viele Unbekannte dazu.

    Freitag, 4. Mai 2018 14:31
  • Zurück zum Thema:

    Von der Hardware interessiert mich noch die Konfiguration des Speichers.
    Dazu gehört nicht nur die Größe sondern auch Typ, Leistung, Bus-Rate.

    Hat der PC z.B. eine Busrate von 1333 und der Server 866 ist das auch nicht zu vernachlässigen.

    Der interne Durchsatz CPU<->Speicher sollte mal unabhängig vom SQL-Server gebenchmarkt werden.

    Auf einem Server laufen häufig insgesamt mehr Prozesse und Threads als auf einer Workstation. Hier kommt es auf das Verhältnis und die Aktivitäten an. Als Werkzeug hilft hier ggf. auch der Performancemonitor.
    Selbst wenn ein Thread nur 1x 1ms je Sekunde benötigt, erfolgt ein Verdrängungswettbewerb auf den CPU's. Hierbei kann es auch zu Core-Wechseln kommen, die natürlich langsamer sind als ein Threadwechsel innerhalb eines Cores (incl. Nachladen des Hauptspeichers in den Cache, s. Busrate).

    Technologisch sorgt ein Server eher für eine gleichmäßigere Auslastung aller CPU's als eine Workstation. Auch dies lässt sich unabhängig vom SQL-Server testen.

    Vielleicht kann der SQL-Server selber ja gar nichts dafür, dass er auf dem Server langsamer ist.

    Nun zu den Abfragen selber:
    Es wurde ja gesagt, dass jede Abfrage einzeln durchgeführt wurde, i.W. also aus dem Cache kommt, keine Parallelisierung erforderlich ist.
    Somit wird ausschließlich ein einziger Thread des SQL-Servers benötigt, alle Paralleleigenschaften der CPU und der Platte fallen weg, es kommt nun nur noch auf die Geschwindigkeit der CPU, des Bus und des Memory an, sowie ggf. parallel laufender weiterer Prozesse auf dem System.
    Auch dies kann man sich ggf. per Threadanzeige des SQL-Servers während der Abfrage ansehen.Ein paar Verwaltungsthreads sind dabei auch immer mal aktiv.

    Letztlich stellt sich die Frage, mit welchem Tool/Programm wird der SQL-Server abgefragt.
    Das Mangagement-Studio ist hierfür nicht die beste Wahl, da man hier keine genauen Aussagen bekommen kann.
    Es wird zwar eine Zeit angezeigt aber diese enthält nicht nur die Queryzeit sondern auch die Ladezeit der Anzeige.

    Hier sollte man mittels Testprogramm, z.B. C#/VB.Net, ein paar kleine Routinen bauen.

    Bleiben wir mal am Beispiel ADO.NET:
    Per CommandObject Execute erhalte ich ein Reader-Object.
    Hier kann man vor dem Execue eine Watchtimer starten und nach dem Execute bereits wieder stoppen.
    Das ist bis auf wenige Kommunikationsteile, die Querydauer des Servers.
    Ggf. kann man eine Abfrage vorab auf eine 1-Satztabelle machen und diese ermittelte Zeit als Basiszeit immer abziehen.

    Wenn man nun den Reader bis zum Ende nur liest, und dieses per Watchtimer überwacht, so erhält man nun die Übertragungszeit zwischen Server und Client als 2. Wert.

    Kommt man nun zur Anwendung:
    Es ist ja schön, eine Datatable direkt mit einem Datareader zu füllen.
    Allerdings kommt hier ein nicht unerheblicher Overhead dazu:
    1. Die Datatable wird mit dem Schema und ggf. vorhandenen Constraints gefüllt, wobei das u.U. erst weitere Abfragen beim Server benötigt.
    2. Die Zeilen werden in die Datatable geladen und dabei auch gegen die Constraints geprüft

    Gerade letzteres ist, da die Daten ja frisch vom Server kommen, kontraproduktiv.
    Leider lassen sich Constraints nur im DataSet, nicht in der Table ausschalten.

    Nun geht es aber mit dem VS-Designer so schön weiter.
    Man erstellt die Controls, z.B. ein DataGrid, eine BindingSource, die man an die Controls hängt und an die Bindingsource hängt man das DataSet oder die Tabelle.

    Und jetzt fängt leider der Schwachsinn bei ADO an:
    Man kann zwar DataTable.BeginLoadData und  EndLoadData angeben, dies verhindert nun aber nicht das Senden der Events der DataTable! Ebenso kann die BindingSource mit SuspendBinding markiert werden, das verhindert aber leider nicht das Senden von Events wie ListChanged.
    D.h., wenn ich nun den Reader die Table füllen lasse, wird der Constraint geprüft, und ein RowInserted/Updated o.ä. gesendet. Dies wird von der BindingSource weitergegeben die dies wiederum an das Grid weitergibt und diese dann erst mal Updates, ggf. auch auf dem Bildschirm, durchführt.

    Wenn man nun glaubt, dies besser selber zu machen gibt es ja 2 Verfahren:
    1. man erstellt ein RowObject aus der DataTable, füllt die Felder und fügt die Zeile der Tabelle hinzu => RowInserted-Event.
    2. man fügt eine Zeile hinzu, => RowInserted, und setzt nun die Feldinhalte, je Feld => RowChanged

    Wenn man dies bei der Anwendung beachtet und anders vorgeht sollte man auch hier Performance gewinnen können.

    Und wie ganz eingangs schon mal geschrieben, kann ein SQL-Server nur dann seine Kapazität nutzen, wenn viele parallele Aktionen auf vielen unterschiedlichen Tabellen durchgeführt werden.
    Es liegt nun mal einfach in der Technologie begründet, dass Zugriffe (weniger lesen, als Update/Insert/Delete) auf eine einzelne Tabelle i.d.R. serialisiert werden müssen da sie immer dieselben Seiten (Daten, Indexe, Constraints, Trigger) betreffen.
    Aber selbst beim Lesen müssen ja offene Transaktionen überlesen, Satzversionen und sonstige Sperren berücksichtigt werden.

    Und da bietet ein Server durch Raid eben auch das parallele Lesen von mehreren Disks mehr Leistung als eine Workstation (voraus gesetzt, der SQL-Server verwendet auch tatsächlich mehrere Threads zum Lesen).
    Bei der Standardversion (Beschränkung auf 1 CPU, 1GB Cache) nützt das dann auch wieder nichts.

    Übrigens für einige Analysen kann der Sysinternals ProcessExplorer verwendet werden. Interessant ist z.B. ob die Datenbankdatei/en mehrfach geöffnet sind. Ist diese nur 1x offen, ist die Wahrscheinlichkeit hoch, das gar keine parallelen physischen Zugriffe erfolgen.

    Und wer beim SQL-Standard die 1GB Cache umgehen will, sollte den Server virtualisieren.
    Bei VirtualBox wird die virtuelle Platte vom Host-Filecache verwaltet.
    Der SQL-Server hat zwar selber nur 1GB Cache, allerdings wird fast jeder Plattenzugriff vom Hostcache befriedigt.
    Ich habe mal einen Disk-Benchmark gemacht. Host-Disk: ca. 300MB/Sec, Virtuelle Disk: 1800MB/Sec.

    Viel Spaß.



    Samstag, 5. Mai 2018 15:01