sql >> データベース >  >> RDS >> Database

おい、その#tempテーブルを所有しているのは誰ですか?

    あなたはおそらく、誰が#tempテーブルの特定のコピーを作成したのか知りたいというシナリオにあったでしょう。 2007年6月に、#tempテーブルをセッションにマップするDMVを要求しましたが、これは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);

    (Jonathan Kehayiasによるコードに基づく)

    スペース使用量を決定するには、これをさらに拡張して、sys.dm_db_partition_statsなどのDMVからのデータを結合することができます。 –例:

    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テーブルがヒープの場合、これは機能しなくなりました。 Bob Ward(@bobwardms)は、これが起こった理由の完全な説明を提供しました。簡単に言うと、デフォルトのトレースから#tempテーブルの作成を除外しようとするロジックにバグがあり、このバグは、トレースと拡張イベントの調整を改善するSQLServer2012の作業中に部分的に修正されました。 SQL Server 2012+は、ヒープではなく、主キーなどのインライン制約を使用して#tempテーブルの作成をキャプチャすることに注意してください。

    [ボブの完全な説明を表示/非表示にするには、ここをクリックしてください。]

    Object:Createdイベントには、実際には、Begin、Commit、およびRollbackの3つのサブイベントがあります。したがって、オブジェクトを正常に作成すると、2つのイベントが発生します。1つはBegin用、もう1つはCommit用です。 EventSubClassを見れば、どれかがわかります。


    SQL Server 2012より前は、Object:Created with subclass=BeginのみにObjectNameが入力されていました。したがって、サブクラス=Commitには、入力されたObjectNameが含まれていませんでした。これは、Beginイベントで名前を検索できるという考えを繰り返さないようにするための設計によるものです。


    前述したように、デフォルトのトレースは、dbid =2でオブジェクト名が「#」で始まるトレースイベントをスキップするように設計されています。したがって、デフォルトのトレースに表示されるのは、Object:Created subclass =Commitイベントです(これが、オブジェクト名が空白である理由です)。


    tempdbオブジェクトをトレースしないという「意図」を文書化していないにもかかわらず、動作が意図したとおりに機能していないことは明らかです。


    次に、SQLServer2012の構築に進みます。SQLTraceからXEventにイベントを移植するプロセスに移ります。このXEvent作業の一環として、この時間枠の間に、subclass=CommitまたはRollbackにObjectNameを入力する必要があると判断しました。これを行うコードは、SQLTraceイベントを生成するコードと同じであるため、SQLTraceイベントにはsubclass=CommitのObjectNameが含まれています。


    また、デフォルトトレースのフィルタリングロジックは変更されていないため、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と2008R2でも同様のことができるかもしれませんが、利用可能なものとは微妙な違いがあることはわかっています。このエラーが発生した後は、テストしませんでした。

    メッセージ25623、レベル16、状態1、行1
    イベント名「sqlserver.object_created」が無効であるか、オブジェクトが見つかりませんでした

    データの分析

    ファイルターゲットから情報を取得することは、デフォルトのトレースを使用する場合よりも少し面倒です。これは主に、すべてがXMLとして保存されているためです(わかりやすく言うと、XMLはNVARCHARとして表示されます)。これは、デフォルトのトレースに対して上記の2番目のクエリと同様の情報を返すために作成したクエリです。注意すべき重要な点の1つは、拡張イベントはデータをUTCで保存するため、サーバーが別のタイムゾーンに設定されている場合は、create_dateになるように調整する必要があります。 sys.objects内 UTCであるかのように比較されます。 (object_idのため、タイムスタンプは一致するように設定されています 値はリサイクルできます。ここでは、リサイクルされた値を除外するには2秒のウィンドウで十分だと思います。)

    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. テーブルからデータ行を削除した後、MySQLInnoDBがディスクスペースを解放しない

    2. Android-SDカード画像をロードする際のより良いアプローチ

    3. TSQL-BEGIN .. ENDブロック内でGOを使用する方法は?

    4. 例を使用したOPENJSONの概要(SQL Server)