Database
 sql >> база данни >  >> RDS >> Database

Пич, кой притежава тази #temp маса?

Вероятно сте попадали в сценарий, в който сте били любопитни кой е създал конкретно копие на таблица #temp. Още през юни 2007 г. поисках DMV, за да съпостави #temp таблици със сесии, но това беше отхвърлено за изданието от 2008 г. (и беше пометено с пенсионирането на Connect преди няколко години).

В SQL Server 2005, 2008 и 2008 R2 трябва да можете да изтеглите тази информация от трасето по подразбиране:

DECLARE @filename VARCHAR(MAX);
 
SELECT @filename = SUBSTRING([path], 0,
 LEN([path])-CHARINDEX('\', REVERSE([path]))+1) + '\Log.trc'  
FROM sys.traces   
WHERE is_default = 1;  
 
SELECT   
     o.name,   
     o.[object_id],  
     o.create_date, 
     gt.SPID,  
     NTUserName = gt.NTDomainName + '\' + gt.NTUserName,
     SQLLogin = gt.LoginName,  
     gt.HostName,  
     gt.ApplicationName,
     gt.TextData -- don't bother, always NULL 
  FROM sys.fn_trace_gettable(@filename, DEFAULT) AS gt  
  INNER JOIN tempdb.sys.objects AS o   
    ON gt.ObjectID = o.[object_id] 
  WHERE gt.DatabaseID = 2 
    AND gt.EventClass = 46 -- (Object:Created Event from sys.trace_events)  
    AND gt.EventSubClass = 1 -- Commit
    AND o.name LIKE N'#%'
    AND o.create_date >= DATEADD(MILLISECOND, -100, gt.StartTime)   
    AND o.create_date <= DATEADD(MILLISECOND,  100, gt.StartTime);

(Въз основа на код от Джонатан Кехайяс.)

За да определите използването на пространство, можете допълнително да подобрите това, за да се присъедините към данни от DMV като sys.dm_db_partition_stats – например:

DECLARE @filename VARCHAR(MAX);
 
SELECT @filename = SUBSTRING([path], 0,
   LEN([path])-CHARINDEX('\', REVERSE([path]))+1) + '\Log.trc'  
FROM sys.traces   
WHERE is_default = 1;  
 
SELECT   
     o.name,   
     o.[object_id],  
     o.create_date, 
     gt.SPID,  
     NTUserName = gt.NTDomainName + '\' + gt.NTUserName,
     SQLLogin = gt.LoginName,  
     gt.HostName,  
     gt.ApplicationName,
     row_count = x.rc,
     reserved_page_count = x.rpc
  FROM sys.fn_trace_gettable(@filename, DEFAULT) AS gt  
  INNER JOIN tempdb.sys.objects AS o   
    ON gt.ObjectID = o.[object_id]
  INNER JOIN
  (
    SELECT 
      [object_id],
      rc  = SUM(CASE WHEN index_id IN (0,1) THEN row_count END), 
      rpc = SUM(reserved_page_count) 
    FROM tempdb.sys.dm_db_partition_stats
    GROUP BY [object_id]
  ) AS x 
    ON x.[object_id] = o.[object_id]
  WHERE gt.DatabaseID = 2 
    AND gt.EventClass = 46 -- (Object:Created Event from sys.trace_events)  
	AND gt.EventSubClass = 1 -- Commit
	AND gt.IndexID IN (0,1)
    AND o.name LIKE N'#%'
    AND o.create_date >= DATEADD(MILLISECOND, -100, gt.StartTime)   
    AND o.create_date <= DATEADD(MILLISECOND,  100, gt.StartTime);

Започвайки от SQL Server 2012 обаче, това спря да работи, ако таблицата #temp беше купчина. Боб Уорд (@bobwardms) предостави подробно обяснение защо това се е случило; краткият отговор е, че имаше грешка в тяхната логика, за да се опитат да филтрират създаването на таблица #temp от проследяването по подразбиране и тази грешка беше частично коригирана по време на работата на SQL Server 2012 за по-добро подравняване на трасиране и разширени събития. Обърнете внимание, че SQL Server 2012+ все още ще улавя създаването на таблица #temp с вградени ограничения като първичен ключ, а не купчини.

[Щракнете тук, за да покажете/скриете пълното обяснение на Боб.]

Събитието Object:Created всъщност има 3 подсъбития:Начало, Комит и Отмяна. Така че, ако създадете успешно обект, получавате 2 събития:1 за Begin и 1 за Commit. Знаете кое, като погледнете EventSubClass.


Преди SQL Server 2012 само Object:Created с подклас =Begin е имало попълнено ObjectName. Така че подкласът =Commit не съдържа попълненото ObjectName. Това беше замислено, за да се избегне повтарянето на това мислене, че можете да потърсите името в събитието Begin.


Както казах, трасирането по подразбиране е проектирано да пропусне всички събития на проследяване, където dbid =2 и името на обекта започват с "#". Така че това, което може да се покаже в трасирането по подразбиране, са събитията Object:Created =Commit (което е причината името на обекта да е празно).


Въпреки че не документирахме нашите „намерения“ да не проследяваме tempdb обекти, поведението очевидно не работи по предназначение.


Сега преминете напред към изграждането на SQL Server 2012. Преминаваме към процес на пренасяне на събития от SQLTrace към XEvent. Решихме през този период от време като част от тази работа на XEvent, че подклас =Commit или Rollback се нуждае от попълване на ObjectName. Кодът, в който правим това, е същият код, в който произвеждаме събитието SQLTrace, така че сега SQLTrace събитието има ObjectName в него за подклас=Commit.


И тъй като нашата логика на филтриране за проследяване по подразбиране не се е променила, сега не виждате нито Begin, нито Commit събития.

Как трябва да го направите днес

В SQL Server 2012 и нагоре разширените събития ще ви позволят ръчно да заснемате object_created събитие и е лесно да добавите филтър, който да се грижи само за имената, които започват с # . Следващата дефиниция на сесията ще улови цялото създаване на таблица #temp, хеп или не, и ще включва цялата полезна информация, която обикновено ще бъде извлечена от трасирането по подразбиране. В допълнение, той улавя SQL пакета, отговорен за създаването на таблицата (ако искате), информацията, която не е налична в проследяването по подразбиране (TextData винаги е NULL ).

CREATE EVENT SESSION [TempTableCreation] ON SERVER 
ADD EVENT sqlserver.object_created
(
  ACTION 
  (
    -- you may not need all of these columns
    sqlserver.session_nt_username,
    sqlserver.server_principal_name,
    sqlserver.session_id,
    sqlserver.client_app_name,
    sqlserver.client_hostname,
    sqlserver.sql_text
  )
  WHERE 
  (
    sqlserver.like_i_sql_unicode_string([object_name], N'#%')
    AND ddl_phase = 1   -- just capture COMMIT, not BEGIN
  )
)
ADD TARGET package0.asynchronous_file_target
(
  SET FILENAME = 'c:\temp\TempTableCreation.xel',
  -- you may want to set different limits depending on
  -- temp table creation rate and available disk space
      MAX_FILE_SIZE = 32768,
      MAX_ROLLOVER_FILES = 10
)
WITH 
(
  -- if temp table creation rate is high, consider
  -- ALLOW_SINGLE/MULTIPLE_EVENT_LOSS instead
  EVENT_RETENTION_MODE = NO_EVENT_LOSS
);
GO
ALTER EVENT SESSION [TempTableCreation] ON SERVER STATE = START;

Може да сте в състояние да направите нещо подобно в 2008 и 2008 R2, но знам, че има някои фини разлики в това, което е налично, и не го тествах, след като получих тази грешка веднага:

Msg 25623, ниво 16, състояние 1, ред 1
Името на събитието, "sqlserver.object_created", е невалидно или обектът не може да бъде намерен

Анализиране на данните

Извличането на информацията от целта на файла е малко по-тромаво, отколкото при проследяването по подразбиране, най-вече защото цялата се съхранява като XML (е, за да бъдем педантични, това е XML, представен като NVARCHAR). Ето една заявка, която направих, за да върна информация, подобна на втората заявка по-горе, спрямо трасето по подразбиране. Едно важно нещо, което трябва да се отбележи е, че Extended Events съхранява данните си в UTC, така че ако сървърът ви е настроен на друга часова зона, ще трябва да настроите така, че create_date в sys.objects се сравнява, сякаш е UTC. (Частотата за време са зададени да съвпадат, защото object_id стойностите могат да бъдат рециклирани. Тук предполагам, че прозорец от две секунди е достатъчен за филтриране на всички рециклирани стойности.)

DECLARE @delta INT = DATEDIFF(MINUTE, SYSUTCDATETIME(), SYSDATETIME());
 
;WITH xe AS
(
  SELECT 
    [obj_name]  = xe.d.value(N'(event/data[@name="object_name"]/value)[1]',N'sysname'),
    [object_id] = xe.d.value(N'(event/data[@name="object_id"]/value)[1]',N'int'),
    [timestamp] = DATEADD(MINUTE, @delta, xe.d.value(N'(event/@timestamp)[1]',N'datetime2')),
    SPID        = xe.d.value(N'(event/action[@name="session_id"]/value)[1]',N'int'),
    NTUserName  = xe.d.value(N'(event/action[@name="session_nt_username"]/value)[1]',N'sysname'),
    SQLLogin    = xe.d.value(N'(event/action[@name="server_principal_name"]/value)[1]',N'sysname'),
    HostName    = xe.d.value(N'(event/action[@name="client_hostname"]/value)[1]',N'sysname'),
    AppName     = xe.d.value(N'(event/action[@name="client_app_name"]/value)[1]',N'nvarchar(max)'),
    SQLBatch    = xe.d.value(N'(event/action[@name="sql_text"]/value)[1]',N'nvarchar(max)')
 FROM 
    sys.fn_xe_file_target_read_file(N'C:\temp\TempTableCreation*.xel',NULL,NULL,NULL) AS ft
    CROSS APPLY (SELECT CONVERT(XML, ft.event_data)) AS xe(d)
) 
SELECT 
  DefinedName         = xe.obj_name,
  GeneratedName       = o.name,
  o.[object_id],
  xe.[timestamp],
  o.create_date,
  xe.SPID,
  xe.NTUserName,
  xe.SQLLogin, 
  xe.HostName,
  ApplicationName     = xe.AppName,
  TextData            = xe.SQLBatch,
  row_count           = x.rc,
  reserved_page_count = x.rpc
FROM xe
INNER JOIN tempdb.sys.objects AS o
ON o.[object_id] = xe.[object_id]
AND o.create_date >= DATEADD(SECOND, -2, xe.[timestamp])
AND o.create_date <= DATEADD(SECOND,  2, xe.[timestamp])
INNER JOIN
(
  SELECT 
    [object_id],
    rc  = SUM(CASE WHEN index_id IN (0,1) THEN row_count END), 
    rpc = SUM(reserved_page_count)
  FROM tempdb.sys.dm_db_partition_stats
  GROUP BY [object_id]
) AS x
ON o.[object_id] = x.[object_id];

Разбира се, това ще върне само пространство и друга информация за #temp таблици, които все още съществуват. Ако искате да видите всички създадени таблици #temp, които все още са налични в целевия файл, дори и да не съществуват сега, просто променете и двата екземпляра на INNER JOIN до LEFT OUTER JOIN .


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как да изчислим пълзящата средна в червено изместване

  2. Използване на множество полета за уникален ключ в Prisma

  3. Промяна на начина, по който isql изпълнява SQL

  4. 2 начина за избор на редове, които съответстват на всички елементи в списък (T-SQL)

  5. Обяснение на производителността на базата данни на Azure SQL и нивата на услуги