… quando non si possono usare le InMemory

Sulle tabelle temporanee e sulle variabili di tipo Table, e dei vantaggi e/o svantaggi delle prime rispetto alle altre, si è già scritto di tutto e di più, ed anche sfatati “strani” miti ( ma entrambe sono nel TempDb ); alla fine per farla breve rimangono a favore delle temporanee un paio di aspetti:

  1. Hanno le statistiche mentre per le variabili ti tipo table (anche InMemory) SQL Server stima sempre 1 riga
  2. Sono visibili da stored procedure richiamate da quella che ha creato la temporanea

Il punto 1 diventa trascurabile nel caso di oggetti con poche righe, mentre gli svantaggi con molte righe possono essere tranquillamente superati con:

  • OPTION (RECOMPILE), ma prestate attenzione al caso di numerose chiamate (anche ogni secondo), altrimenti si spreca più CPU per ricompilare gli statement che non eseguirli.
  • TRACE FLAG 2453, ma poi l’impostazione è per tutto il server, e per quanto riguarda la CPU si ricade nel punto precedente per tutte le stored procedure che usano variabili di tipo table.

Fatte queste premesse vediamo ora un approccio molto più pratico del confronto, che riguarda 2 aspetti fondamentali nelle performance del TempDb quando si ha a che fare con “High Workload Scenarios”;

  • PAGELATCH contention, EX e SH, nelle pagine PFS ( 1 e ogni 8088, 64Mb ), GAM e SGAM ( 2 e 3, e ogni 511230 pagine, 4Gb)
  • Traffico nel Tlog, generato dai record scritti, che alla fine si traduce in MB/s.

Per dimostrare le differenze userò due stored procedure che creano una tabella temporanea e una variabile di tipo table, inserendo 5 righe per ognuna; la loro struttura ed il numero di righe medie rispecchia una situazione tipica dei nostri ambienti; una sessione Extended Events e una query sul Tlog del Tempdb mostreranno le differenze.

CREATE PROCEDURE [dbo].[proc_TestTemp]
AS
BEGIN
SET NOCOUNT ON
CREATE TABLE #Table1(
[Fld1] [bigint] NULL,
[Fld2] [int] NULL,
[Fld3] [int] NULL,
[Fld4] [tinyint] NULL,
[Fld5] [decimal](9, 2) NULL,
[Fld6] [decimal](9, 2) NULL,
[Fld7] [tinyint] NULL,
[Fld8] [varchar](15) ,
INDEX [IX_Fld1] CLUSTERED ([Fld1] ASC))       

INSERT INTO #Table1 VALUES (1,100000,200000,35,9.20,9.20, 3, 'Prova')
INSERT INTO #Table1 VALUES (10,100000,200000,35,9.20,9.20, 3, 'Prova')
INSERT INTO #Table1 VALUES (100,100000,200000,35,9.20,9.20, 3, 'Prova')
INSERT INTO #Table1 VALUES (1000,100000,200000,35,9.20,9.20, 3, 'Prova')
INSERT INTO #Table1 VALUES (1000,100000,200000,35,9.20,9.20, 3, 'Prova')
END
GO       

CREATE PROCEDURE [dbo].[proc_TestVar]
AS
BEGIN
SET NOCOUNT ON
DECLARE @Table1 TABLE (
[Fld1] [bigint] NULL,
[Fld2] [int] NULL,
[Fld3] [int] NULL,
[Fld4] [tinyint] NULL,
[Fld5] [decimal](9, 2) NULL,
[Fld6] [decimal](9, 2) NULL,
[Fld7] [tinyint] NULL,
[Fld8] [varchar](15),
INDEX [IX_Fld1] CLUSTERED ([Fld1] ASC))

INSERT INTO @Table1 VALUES (1,100000,200000,35,9.20,9.20, 3, 'Prova')
INSERT INTO @Table1 VALUES (10,100000,200000,35,9.20,9.20, 3, 'Prova')
INSERT INTO @Table1 VALUES (100,100000,200000,35,9.20,9.20, 3, 'Prova')
INSERT INTO @Table1 VALUES (1000,100000,200000,35,9.20,9.20, 3, 'Prova')
INSERT INTO @Table1 VALUES (1000,100000,200000,35,9.20,9.20, 3, 'Prova')
END
GO

La sessione Extended Events usa l’evento sqlserver.latch_acquired filtrato per il dbid 2 e la sessione dalla quale si eseguono le due sp.

CREATE EVENT SESSION [Latch]
ON SERVER
ADD EVENT sqlserver.latch_acquired(
ACTION(sqlserver.session_id,sqlserver.sql_text)
WHERE ([package0].[equal_uint64]([database_id],(2))
AND [sqlserver].[session_id]=(57)))
ADD TARGET package0.event_file(SET filename=N'Latch'),
ADD TARGET package0.ring_buffer(SET max_memory=(40960))
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY=1 SECONDS, MAX_EVENT_SIZE=0 KB,
MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=OFF)
GO


Mentre la query sul Tlog è una cosa del tipo

SELECT
    fd.[Current LSN],
    fd.Operation,
    fd.AllocUnitName,
    fd.[Transaction Name],
    fd.[Transaction ID]
FROM sys.fn_dblog(NULL, NULL) AS fd


Mettendo tutto insieme ed eseguendo un checkpoint prima di iniziare vediamo cosa succede con la tabella temporanea…

USE tempdb
GO
CHECKPOINT
GO
ALTER EVENT SESSION Latch ON SERVER STATE = start
GO
exec [TestDb].[dbo].[proc_TestTemp]
GO
ALTER EVENT SESSION Latch ON SERVER STATE = stop
GO
SELECT
    fd.[Current LSN],
    fd.Operation,
    fd.AllocUnitName,
    fd.[Transaction Name],
    fd.[Transaction ID]
FROM sys.fn_dblog(NULL, NULL) AS fd

La query sul transaction log ci dice che sono stati scritti 38 log record ( 138 alla prima esecuzione )
image
Mentre per la sessione Extended Events sono stati acquisiti un totale di 51 Latch
image
Cancelliamo adesso i file della sessione Extended Events e ripetiamo la stessa cosa per la sp che usa la table variabile

Questa volta la query sul Tlog dice che i record scritti sono 23 ( 240 alla prima esecuzione, quindi mettere in cache una Table Variable genera più record)
image
Mentre la sessione Extended Events dice che i Latch acquisiti in totale sono solo 11
Cattura

 

Quindi in sostanza abbiamo che le variabili di tipo Table richiedono molti meno LATCH e record nel Tlog, condizione che nel caso di migliaia di chiamate al minuto può fare una differenza notevole. Se la query sul Transaction Log viene modificata per prendere una SUM ([Log Record Lenght]) abbiamo che la stored procedure che usa la tabella temporanea usa 5552 bytes, mentre l’altra 2632 bytes.

Vediamo adesso cosa succede prendendo come riferimento i contatori di Performance “Log Flushes/sec” e “Log Bytes Flushed/sec” dell’oggetto “Databases, istanza tempdb: a tal proposito ho configurato il tool SQL Load Generator per generare (solo) circa 130 Batch al secondo con le due stored appena viste.

Nel caso della stored procedure con tabella temporanea abbiamo il seguente risultato
image
16 Log Flushes al secondo e circa 650Kb/sec scritti nel tlog.
Passando alla varsione con variabile di tipo table otteniamo invece
image
I Log Flushes/sec sono dimezzati a 8 e i Log Bytes Flushed /sec più che dimezzati a 300Kb/sec.

Dal punto di vista pratico quindi,nel momento in cui non si possa abilitare l’ InMemory, è più conveniente affidarsi in prima istanza alle variabili ti tipo table, soprattutto quando si ha a che fare con poche righe e molte chiamate al minuto, e nei casi in cui la differenza di prestazioni volga a favore delle tabelle temporanee provare una RECOMPILE che molto spesso livella le prestazioni dei due oggetti.

 

In this article I’ll write nothing new regarding monitoring, but rather how to use what SQL Server already provide us and make it as light as possible; when working in “high OLTP workload” environments monitoring should be almost “invisible”, and by “high OLTP workload” I mean a server running at 50000/60000 Batches/sec

image

with the load made almost by stored procedures in the average CPU time from few ms to few tens of ms. Just for an example, the image below shows 10 minutes sampling taken at about 1/4 maximum load of one customer, with the first column showing the CPU time of every stored procedure over the total SQL Server CPU usage; so, if for example SQL was at 40% the first sp was responsible for the 19.8% of that load, and so on..

image

The above report is obtained from a table populated every minute with a simple query over the sys.dm_exec_procedure_stats

SELECT dateadd(mi, datediff(mi, 0, getdate()), 0), sql_handle,
plan_handle, total_elapsed_time, total_worker_time,
total_logical_reads, total_logical_writes, execution_count
FROM sys.dm_exec_procedure_stats

In addition to this, on our servers we usually capture every minute a snapshot over the sys.dm_exec_requests to get a photo of the running statements, and use it also in realtime when there is something that “smells” strange Sorriso; for this purpose we initially used the famous sp_WhoIsActive, very powerfull, but sometimes, when there were several process running or some blocking conditions, it happened that it was lasting too much or, never ending; sometimes it caused also an excessive latch contention on the tempdb and definitively slowed down the instance.

After digging a bit into the issue it came out to be the high usage of string manipulation functions which caused heavy usage of the TempDb and CPU time, that’s why the idea of something “lighter” and with only the functionality needed for our purpose.

Basycally we use a statement like this

SELECT s.host_name, r.session_id as 'session', r.blocking_session_id as [blocked by],
CAST  (SUBSTRING(st.text, (r.statement_start_offset/2)+1,
    ((CASE r.statement_end_offset
      WHEN -1 THEN DATALENGTH(st.text)
     ELSE r.statement_end_offset
     END - r.statement_start_offset)/2) + 1) as text) AS statement_text,  ISNULL(cast(OBJECT_SCHEMA_NAME(st.objectid, st.dbid) + '.' + OBJECT_NAME(st.objectid, st.dbid) as varchar(100)), 'AdHoc Statement') as object_name,
     s.login_name,
     CASE  WHEN LEFT(s.program_name, 8) = 'SQLAgent' then
                (SELECT 'SQLAgent Job: ' + b.name from msdb.dbo.sysjobs b WHERE (SUBSTRING(MASTER.dbo.FN_VARBINTOHEXSTR(CONVERT(VARBINARY(16), b.JOB_ID)),1,10)) = SUBSTRING(s.PROGRAM_NAME,30,10))
        ELSE s.program_name
    END AS [program_name],
r.start_time as request_start_time,r.last_wait_type,r.wait_time,
r.cpu_time, r.total_elapsed_time,
r.logical_reads, r.reads as physical_reads, r.status as request_status
FROM sys.dm_exec_sessions s left join sys.dm_exec_requests r on s.session_id = r.session_id
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS st
WHERE r.session_id > 50 and r.session_id  @@SPID
AND last_wait_type  'SP_SERVER_DIAGNOSTICS_SLEEP'
ORDER BY cpu_time DESC

for cehcking in realtime what is currently running,  with an added

OUTER APPLY sys.dm_exec_text_query_plan( r.plan_handle,
r.statement_start_offset, r.statement_end_offset) tp

when we want to take a look at the Execution Plan ( CAST (tp.query_plan as xml) AS plan_xml in the fields list).

Second, we check for blocking processes with something like this

SELECT    s.session_id, r.blocking_session_id AS [blocked by], CAST(SUBSTRING(st.text, (r.statement_start_offset / 2) + 1,
                ((CASE r.statement_end_offset
                    WHEN - 1 THEN DATALENGTH(st.text)
                    ELSE r.statement_end_offset
                END - r.statement_start_offset) / 2) + 1) AS text) AS statement_text,
                ISNULL(cast(OBJECT_SCHEMA_NAME(st.objectid, st.dbid) + '.' + OBJECT_NAME(st.objectid, st.dbid)
                         AS varchar(100)), 'AdHoc Statement') AS object_name,
                s.login_name,
                CASE
                WHEN LEFT(s.program_name, 8) = 'SQLAgent' then
                        (SELECT 'SQLAgent Job: ' + b.name from msdb.dbo.sysjobs b WHERE (SUBSTRING(MASTER.dbo.FN_VARBINTOHEXSTR(CONVERT(VARBINARY(16), b.JOB_ID)),1,10)) = SUBSTRING(s.PROGRAM_NAME,30,10))
                ELSE s.program_name
                END AS [program_name], r.start_time AS request_start_time, r.last_wait_type, r.cpu_time, r.total_elapsed_time, r.logical_reads,
                r.reads AS physical_reads, r.status AS request_status, r.wait_time, r.command, master.dbo.fn_varbintohexstr(r.plan_handle) AS plan_handle
        FROM sys.dm_exec_sessions s
            LEFT JOIN sys.dm_exec_requests r
            ON r.session_id = s.session_id
            OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS st
        Where  S.Session_ID In
            (
                 Select Blocking_Session_ID
                 From Sys.DM_Exec_Requests (READUNCOMMITTED)
                 Where Blocking_Session_ID  0
            )

Finally, becasue TempDb is a precious resource we introduced a check for long running transactions which can block the checkpoint process and cause a huge increase of the TLog size and, why not, exhaust the disk space. Just remember that on the TempDb the checkpoint happens when the TLog is at 70% usage and any open (long) transaction delay the process, and could start an “avalanche effect”; several times it does not depend on how much data You write in the TempDb but rather how much that transaction last

This script can also be changed for checking a different database name

SELECT  ISNULL(cast(tst.session_id as varchar),'Internal') as SessionID,
    tdt.database_transaction_log_bytes_reserved/1024 AS [TlogKB],
    tat.transaction_id AS [Transacton ID],
    tat.name      AS [TRANSACTION Name],
    tat.transaction_begin_time AS [TRANSACTION BEGIN TIME],
    DATEDIFF(mi, tat.transaction_begin_time, GETDATE()) AS [Elapsed TIME (in MIN)],
    CASE tat.transaction_type
        WHEN 1 THEN 'Read/write'
        WHEN 2 THEN 'Read-only'
        WHEN 3 THEN 'System'
        WHEN 4 THEN 'Distributed'
        END AS [TRANSACTION Type],
    CASE tat.transaction_state
        WHEN 0 THEN 'The transaction has not been completely initialized yet.'
        WHEN 1 THEN 'The transaction has been initialized but has not started.'
        WHEN 2 THEN 'The transaction is active.'
        WHEN 3 THEN 'The transaction has ended. This is used for read-only transactions.'
        WHEN 4 THEN 'The commit process has been initiated on the distributed transaction. This is for distributed transactions only. The distributed transaction is still active but further processing cannot take place.'
        WHEN 5 THEN 'The transaction is in a prepared state and waiting resolution.'
        WHEN 6 THEN 'The transaction has been committed.'
        WHEN 7 THEN 'The transaction is being rolled back.'
        WHEN 8 THEN 'The transaction has been rolled back.'
        END AS [TRANSACTION Description],
    CAST  (SUBSTRING(t.text, (r.statement_start_offset/2)+1,
    ((CASE r.statement_end_offset
        WHEN -1 THEN DATALENGTH(t.text)
        ELSE r.statement_end_offset
    END - r.statement_start_offset)/2) + 1) as text) AS statement_text
FROM sys.dm_tran_active_transactions tat
    INNER JOIN sys.dm_tran_database_transactions tdt
        on tat.transaction_id=tdt.transaction_id
    LEFT JOIN sys.dm_tran_session_transactions AS tst
        ON tdt.transaction_id = tst.transaction_id
    LEFT OUTER JOIN sys.dm_exec_requests AS r
         ON tst.session_id = r.session_id
    OUTER APPLY sys.dm_exec_sql_text(r.plan_handle) AS t
WHERE tdt.database_id=DB_ID('tempdb') and tdt.database_transaction_log_bytes_reserved > 0
ORDER BY 2 DESC     

Putting it all together I wrote a sp with 3 input parameters and named it sp_WhatIsRunning

USE master
GO
CREATE PROC [dbo].[sp_WhatIsRunning]
(
    @ShowPlan Bit = 0,
    @ShowLock Bit = 0,
    @ShowTempLog Bit = 0
)
AS
BEGIN
    SET NOCOUNT ON
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

    IF (@ShowPlan = 0)
    BEGIN
        SELECT s.host_name, r.session_id as 'session', r.blocking_session_id as [blocked by],
        CAST  (SUBSTRING(st.text, (r.statement_start_offset/2)+1,
            ((CASE r.statement_end_offset
             WHEN -1 THEN DATALENGTH(st.text)
            ELSE r.statement_end_offset
        END - r.statement_start_offset)/2) + 1) as text) AS statement_text,  ISNULL(cast(OBJECT_SCHEMA_NAME(st.objectid, st.dbid) + '.' + OBJECT_NAME(st.objectid, st.dbid) as varchar(100)), 'AdHoc Statement') as object_name,
        s.login_name,
        CASE  WHEN LEFT(s.program_name, 8) = 'SQLAgent' then
                    (SELECT 'SQLAgent Job: ' + b.name from msdb.dbo.sysjobs b WHERE (SUBSTRING(MASTER.dbo.FN_VARBINTOHEXSTR(CONVERT(VARBINARY(16), b.JOB_ID)),1,10)) = SUBSTRING(s.PROGRAM_NAME,30,10))
            ELSE s.program_name
        END AS [program_name],
        r.start_time as request_start_time,r.last_wait_type,r.wait_time,
        r.cpu_time, r.total_elapsed_time,
        r.logical_reads, r.reads as physical_reads, r.status as request_status
        FROM sys.dm_exec_sessions s left JOIN sys.dm_exec_requests r ON s.session_id = r.session_id
        CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS st
        WHERE r.session_id > 50 AND r.session_id  @@SPID
        AND last_wait_type  'SP_SERVER_DIAGNOSTICS_SLEEP'
        ORDER BY cpu_time DESC
    END
    ELSE
    BEGIN
    SELECT s.host_name, r.session_id as 'session', r.blocking_session_id as [blocked by],
    CAST  (SUBSTRING(st.text, (r.statement_start_offset/2)+1,
        ((CASE r.statement_end_offset
         WHEN -1 THEN DATALENGTH(st.text)
        ELSE r.statement_end_offset
        END - r.statement_start_offset)/2) + 1) as text) AS statement_text,  ISNULL(cast(OBJECT_SCHEMA_NAME(st.objectid, st.dbid) + '.' + OBJECT_NAME(st.objectid, st.dbid) as varchar(100)), 'AdHoc Statement') as object_name,
        s.login_name,
             CASE  WHEN LEFT(s.program_name, 8) = 'SQLAgent' then
                        (SELECT 'SQLAgent Job: ' + b.name from msdb.dbo.sysjobs b WHERE (SUBSTRING(MASTER.dbo.FN_VARBINTOHEXSTR(CONVERT(VARBINARY(16), b.JOB_ID)),1,10)) = SUBSTRING(s.PROGRAM_NAME,30,10))
                ELSE s.program_name
            END AS [program_name],         r.start_time as request_start_time,r.last_wait_type,r.wait_time,
        r.cpu_time, r.total_elapsed_time,
        r.logical_reads, r.reads as physical_reads, r.status as request_status,
        CAST (tp.query_plan as xml) AS plan_xml
        from sys.dm_exec_sessions s left join sys.dm_exec_requests r on s.session_id = r.session_id
        CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS st
        OUTER APPLY sys.dm_exec_text_query_plan( r.plan_handle, r.statement_start_offset, r.statement_end_offset) tp
        WHERE r.session_id > 50 and r.session_id  @@SPID
        AND last_wait_type  'SP_SERVER_DIAGNOSTICS_SLEEP'
        ORDER BY cpu_time DESC
    END

    IF (@ShowLock=1)
    BEGIN
        SELECT        s.session_id, r.blocking_session_id AS [blocked by], CAST(SUBSTRING(st.text, (r.statement_start_offset / 2) + 1, ((CASE r.statement_end_offset WHEN - 1 THEN DATALENGTH(st.text)
                         ELSE r.statement_end_offset END - r.statement_start_offset) / 2) + 1) AS text) AS statement_text, ISNULL(cast(OBJECT_SCHEMA_NAME(st.objectid, st.dbid) + '.' + OBJECT_NAME(st.objectid, st.dbid)
                         AS varchar(100)), 'AdHoc Statement') AS object_name, s.login_name,
             CASE  WHEN LEFT(s.program_name, 8) = 'SQLAgent' then
                        (SELECT 'SQLAgent Job: ' + b.name from msdb.dbo.sysjobs b WHERE (SUBSTRING(MASTER.dbo.FN_VARBINTOHEXSTR(CONVERT(VARBINARY(16), b.JOB_ID)),1,10)) = SUBSTRING(s.PROGRAM_NAME,30,10))
                ELSE s.program_name
            END AS [program_name]                         , r.start_time AS request_start_time, r.last_wait_type, r.cpu_time, r.total_elapsed_time, r.logical_reads,
                         r.reads AS physical_reads, r.status AS request_status, r.wait_time, r.command, master.dbo.fn_varbintohexstr(r.plan_handle) AS plan_handle
                        FROM sys.dm_exec_sessions s
                            LEFT JOIN sys.dm_exec_requests r
                            ON r.session_id = s.session_id
                         OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS st
        Where  S.Session_ID In
            (
                 SELECT Blocking_Session_ID
                    FROM Sys.DM_Exec_Requests (READUNCOMMITTED)
                 WHERE Blocking_Session_ID  0
            )
    END
    IF (@ShowTempLog =1)
            BEGIN
            SELECT  ISNULL(cast(tst.session_id as varchar),'Internal') as SessionID,
            tdt.database_transaction_log_bytes_reserved/1024 AS [TlogKB],
            tat.transaction_id AS [Transacton ID],
            tat.name      AS [TRANSACTION Name],
            tat.transaction_begin_time AS [TRANSACTION BEGIN TIME],
            DATEDIFF(mi, tat.transaction_begin_time, GETDATE()) AS [Elapsed TIME (in MIN)],
            CASE tat.transaction_type
                WHEN 1 THEN 'Read/write'
                WHEN 2 THEN 'Read-only'
                WHEN 3 THEN 'System'
                WHEN 4 THEN 'Distributed'
                END AS [TRANSACTION Type],
            CASE tat.transaction_state
                WHEN 0 THEN 'The transaction has not been completely initialized yet.'
                WHEN 1 THEN 'The transaction has been initialized but has not started.'
                WHEN 2 THEN 'The transaction is active.'
                WHEN 3 THEN 'The transaction has ended. This is used for read-only transactions.'
                WHEN 4 THEN 'The commit process has been initiated on the distributed transaction. This is for distributed transactions only. The distributed transaction is still active but further processing cannot take place.'
                WHEN 5 THEN 'The transaction is in a prepared state and waiting resolution.'
                WHEN 6 THEN 'The transaction has been committed.'
                WHEN 7 THEN 'The transaction is being rolled back.'
                WHEN 8 THEN 'The transaction has been rolled back.'
                END AS [TRANSACTION Description],
            CAST  (SUBSTRING(t.text, (r.statement_start_offset/2)+1,
            ((CASE r.statement_end_offset
                WHEN -1 THEN DATALENGTH(t.text)
                ELSE r.statement_end_offset
            END - r.statement_start_offset)/2) + 1) as text) AS statement_text
        FROM sys.dm_tran_active_transactions tat
            INNER JOIN sys.dm_tran_database_transactions tdt
                on tat.transaction_id=tdt.transaction_id
            LEFT JOIN sys.dm_tran_session_transactions AS tst
                ON tdt.transaction_id = tst.transaction_id
            LEFT OUTER JOIN sys.dm_exec_requests AS r
                 ON tst.session_id = r.session_id
            OUTER APPLY sys.dm_exec_sql_text(r.plan_handle) AS t
        WHERE tdt.database_id=DB_ID('tempdb') and tdt.database_transaction_log_bytes_reserved > 0
        ORDER BY 2 DESC
    END
END     

You can call it simply with

  • exec sp_WhatIsRunning to get a list of running processes
  • exec sp_WhatIsRunning 1 to add the Execution Plan to the output
  • exec sp_WhatIsRunning 0,1 to show the list of running together with blocking/blocked processes
  • exec sp_WhatIsRunning 0,0,1 to show the list of running processes together with the open transactions list on the TempDb
  • exec sp_WhatIsRunning 1,1,1 to show all; running processes, blocked/blocking and TempDb active transactions

This is just a starting point, we created the sp this way because this is what we need for realtime monitoring, but the it can be expanded with additional fields and/or visualization options.

e come sempre alcune misure

Sebbene non sia mai stato un amante degli amplificatori in Classe D un po’ di tempo fa ho iniziato ad interessarmi dei moduli PowerSoft delle serie Digimod. l’idea era di utilizzare inizialmente un modulo Digimod 500 per un Subwoofer Home Theatre basato sull’altoparlante CIARE HS251, un 10” dall’ottimo rapporto qualità prezzo, e poi un modulo Digimod 1000 per un diffusore amplificato da impiegare anche come monitor, basato sul woofer 18Sound 12W500 in versione 4Ohm e Tromba 18Sound XR1064 pilotata da un driver RCF CD350. La scelta in questo caso è caduta su 18Sound per il fatto che il 12W500, nonostante l’elevata efficienza, non ha confronti in termini di estensione della risposta a parità di volume con altri modelli e/o marche, e perché la tromba è facilmente ruotabile di 90° per l’impiego come monitor e presenta una dispersione abbastanza ridotta su entrambe i piani per essere un componente “tradizionale”; ho scelto poi il  CD350 sinceramente per sperimentare cosa può essere in grado di fare un driver con bobina da 1,75” dal prezzo molto concorrenziale (69€ da www.rossinimusica.it).

Tornando ai moduli Digimod ho acquistato inizialmente il 500 da www.ggsound.it e successivamente i Digimod 1000 completi di Intergration Kit ed i cavi e gli accessori necessari alla programmazione tramite Armonia da laboratoriomusica.com di Vanis Dondi, dove ho trovato i prezzi migliori credo anche per gli altoparlanti 18Sound; Vanis inoltre è sempre stato molto gentile e disponibile sulle mie richieste di delucidazioni. Successivamente ho trovato altri 2 Digimod 1000 e due Digimod 1000NPS su Mercatino Musicale ad un prezzo molto vantaggioso.

I moduli permettono configurazioni molto flessibili in quanto praticamente tutti hanno a disposizione un connettore per collegare un modulo 1000NPS ed espandere cosi le possibilità di configurazione; il modulo DSP fornito con l’Integration Kit ha a disposizione una terza via sulle uscite 3 e 4 che permette di realizzare anche sistemi a 3 vie di notevole potenza, ad esempio con un 500 + 1000NPS oppure 1000+1000NPS con quest’ultimo ad esempio in bridge su 8Ohm.

Per quanto riguarda il loro impiego in campo professionale per quello che ne so, cercando un po’ qua e la nella rate, sono usati in diversi diffusori RCF oltre all’impiego in modo diffuso dei moduli IcePower della B&O; il DIGIMOD 1000 ad esempio è utilizzato nel TTL12-AS, mentre il TTS56-A usa 2 DIGIMOD 3000PFC.

Trattandosi naturalmente di moduli in classe D la potenza massima viene dichiarata nelle condizioni tipiche per questo tipo di configurazione, quindi con le specifiche EIAJ e all’ 1% di distorsione, come avviene anche per i finali di note marche; ma Powersoft in un documento reperibile online per questa linea “tradizionale” di moduli ha il pregio di elencare anche le potenze RMS erogabili dai moduli ed è a queste che farò riferimento nei miei test.

image

Notiamo quindi che per il più piccolo Digimod 500 si dichiara una potenza di 260W RMS con carico da 8Ohm e 450W RMS per 4Ohm, unitamente alla specifiche EIAJ un po’ più permissive; inoltre vengono riportati anche i limiti quando usato insieme ad un Digimod 1000NPS, al quale fornisce l’alimentazione.

Di seguito un paio di immagini del Digimod 500 montato sul dissipatore fornito insieme all’ Integration Kit, pronto per essere testato al banco.

20150604_193847

20150610_083146

Con il solito test 3s On e 15s Off a 100Hz il modulo in questione mostra ancora un’ onda pulita con 45.1V RMS con carico di 8Ohm, corrispondenti a 254W.

20150610_185227

I primi segnali di clipping arrivano a 46.4V corrispondenti a 270W

20150610_185354

L’onda è ancora molto “composta”, senza quel tipico taglio netto del clipping della maggior parte dei  finali tradizionali, e soprattutto molto simmetrico.

Passando a 4Ohm si nota che l’onda è ancora pulita a 41.1V RMS (l’immagine è un po’ mossa) corrispondenti a 422W RMS

20150610_190006

Mentre i primi segni del clipping si notano a circa 42.7V RMS, corrispondenti a 455W RMS, tra l’altro di nuovo con un principio di clipping molto simmetrico e abbastanza morbido; non sono in grado di misurarla ma con questa forma d’onda la distorsione è ancora molto bassa.

20150610_190046

Direi quindi un buon comportamento, considerando che si tratta di misure reali e non da laboratorio, quindi innanzitutto con tensione non stabilizzata e con cavo di alimentazione dell’ampli lungo circa 1,5m preso da un distributore di tensione che va alla presa a muro con un cavo da 3m , tutto con sezione di 2,5mm2.

A breve cercherò di pubblicare anche i test del modulo DIGIMOD 1000, unitamente a quelli con il 1000NPS ed in varie combinazioni; ad esempio, per simulare una tipico diffusore commerciale, una via che pilota un carico di 4Ohm (woofer) e l’altra che ne pilota uno da 8 o 16Ohm (Driver), con ad esempio una differenza di livello di 3/6dB tra le due vie che normalmente viene usata per compensare le differenze di efficienza unitamente all’equalizzazione.

Aggiornamento del 07/04/2017

In questi giorno sono riuscito a mettere un po’ al banco il DIGIMOD 1000, anche se al momento solo da solo e non accoppiato al modulo 1000NPS. Con il solito test ma con un periodo di ON un po’ più lungo del precedente, 4/5s contro 3s della precedente prova, ma sempre a 100Hz

Con un carico stereo da 8Ohm a 44V RMS (231W) per canale abbiamo ancora l’onda pulita nonostante il led del clipping sul modulo si sia già acceso: l’onda infine inizia a “piegarsi” poco oltre i 44V RMS (240W)

20170405_124610

Impostando invece uno dei canali con un livello di 3dB più basso  abbiamo l’onda ancora pulita a 44.3V RMS corrispondenti  a 245W, con l’altro canale che eroga poco più di 120W. Quindi ipotizzando di pilotare la classica configurazione MidWoofer+Driver tutto 8Ohm possiamo realizzare un diffusore/monitor da 300W RMS “reali” ed anche qualcosa di più, considerando che tra equalizzazione ed attenuazione di un driver da 1,75” la seconda via tipicamente erogherà da 1/2 ad 1/4 della potenza disponibile. Nonostante questo comunque consiglio di impostare il limiter della vie dedicata ai medioalti a circa 50W RMS che è anche la tipica potenza supportata da questo tipo di driver; per modelli invece con bobina da 2.5” / 3” tipicamente si viaggia tra i 90W e i 110W RMS.

Passando ai 2 canali a 4Ohm abbiamo l’onda pulita a 39,3V RMS corrispondenti a circa 386W RMS ed i primi segni di clipping a 40.3V RMS corrispondenti a circa 406W RMS; con questo test dopo circa 4s il limiter interno riduce la potenza RSM a circa 250W.

20170405_132746

20170405_133226

Impostando invece uno dei due canali con un guadagno a –3dB o –6dB rispetto all’altro i 40.3V RMS diventano puliti ed il clipping iniza ad apparire a circa 41V RMS (420W). Quindi nel caso in cui l’ipotetico due vie di cui sopra si ritrova con un MidWoofer da 4Ohm riusciamo a realizzare un sistema da 450W RMS reali con un driver da 1,75” e 500W nel caso di impiego di un componente con bobina da 2,5”/3”. Non ho provato la potenza in bridge su 8Ohm ma dal momento che abbiamo il dato in stereo su 4Ohm il conto è presto fatto.

Anche per il DIGIMOD 1000 quindi le specifiche sono confermate; a breve la prova de vari moduli combinati tra di loro.

Aggiornamento del 27/01/2018

In questi giorni sono riuscito a testare il DIGIMOD 1000 collegato al DIGIMOD 1000NPS, in configurazione bridge, quindi 2 canali da 8Ohm; in questa modalità i primi segni del clipping arrivano a 34.9V RMS per ramo, quindi 69.8V RMS totali, corrispondendti a crica 610W/8Ohm per canale, praticamente di nuovo in linea con le specifiche per quella configurazione (4 x 310W/4Ohm)

20170924_115722_001_1516997922078

20171119_185341_001_1516997921443

Ipotizzando quindi l’utilizzo di un DIGIMOD 500 collegato ad un 1000NPS i 3x 350W/4Ohm sono a tutti gli effetti un valore realistico, oppre 1 x 700/8Ohm (e anche più) + 1 x 230/8Ohm