O que a coluna "lê" em sys.dm_exec_sessions realmente indica?

10

Isso pode parecer uma pergunta muito básica, e de fato deveria ser. No entanto, como fã do método científico, gosto de criar uma hipótese e testá-la para ver se estou correta. Nesse caso, estou tentando entender melhor a saída sys.dm_exec_sessionse, mais especificamente, a única coluna "lê".

Os Manuais Online do SQL Server especificam isso secamente como:

Número de leituras realizadas, por solicitações nesta sessão, durante esta sessão. Não é anulável.

Pode-se presumir que isso indica o número de páginas lidas do disco para satisfazer as solicitações emitidas por esta sessão desde o início da sessão. Essa é a hipótese que pensei em testar.

A logical_readscoluna nessa mesma tabela é definida como:

Número de leituras lógicas que foram executadas na sessão. Não é anulável.

Por experiência usando o SQL Server, acredito que esta coluna reflete o número de páginas que foram lidas no disco e na memória . Em outras palavras, o número total de páginas lidas pela sessão, não importa onde essas páginas residam. O diferencial, ou proposição de valor, de ter duas colunas separadas que oferecem informações semelhantes parece ser a capacidade de entender a proporção de páginas lidas no disco ( reads) versus as lidas no cache do buffer ( logical_reads) para uma sessão específica.

No meu equipamento de teste, criei um novo banco de dados, criei uma única tabela com um número conhecido de páginas de dados e depois li essa tabela em uma nova sessão. Então olhei sys.dm_exec_sessionspara ver o que as colunas readse logical_readsdisseram sobre a sessão. Neste ponto, estou confuso com os resultados. Talvez alguém aqui possa esclarecer isso para mim.

O equipamento de teste:

USE master;
IF EXISTS (SELECT 1
    FROM sys.databases d 
    WHERE d.name = 'TestReads')
BEGIN
    ALTER DATABASE TestReads SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
    DROP DATABASE TestReads;
END
GO
CREATE DATABASE TestReads;
GO
ALTER DATABASE TestReads SET RECOVERY SIMPLE;
BACKUP DATABASE TestReads TO DISK = 'NUL:'; /* ensure we are in 
                                            simple recovery model */
GO

USE TestReads;
GO

/*
    create a table with 2 rows per page, for easy math!
*/
CREATE TABLE dbo.TestReads
(
    ID INT NOT NULL
        CONSTRAINT PK_TestReads
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , SomeData CHAR(4000) NOT NULL
);

/*
    insert 5000 pages of data
*/
INSERT INTO dbo.TestReads (SomeData)
SELECT TOP(10000) o1.name
FROM sys.objects o1
    , sys.objects o2
    , sys.objects o3
ORDER BY o1.object_id
    , o2.object_id
    , o3.object_id;


/*
    Verify we have 5,000 pages of data, with 10,000 rows.
*/
SELECT o.name
    , p.rows
    , au.total_pages
    , au.used_pages
    , au.data_pages
FROM sys.partitions p
    INNER JOIN sys.objects o ON p.object_id = o.object_id 
    INNER JOIN sys.allocation_units au 
        ON p.hobt_id = au.container_id 
        AND (au.type = 1 or au.type = 0)
WHERE p.index_id = 1
    AND o.name = 'TestReads'
    AND o.type = 'U';

/*
    issue a checkpoint to ensure dirty pages are flushed to disk
*/
CHECKPOINT 30;
DBCC DROPCLEANBUFFERS;
DBCC FREESYSTEMCACHE ('ALL');
DBCC FREEPROCCACHE;
DBCC FREESESSIONCACHE;
GO

/*
    ensure we have no data cached in memory for the TestReads database
*/
USE master;
ALTER DATABASE TestReads SET OFFLINE WITH ROLLBACK IMMEDIATE;
ALTER DATABASE TestReads SET ONLINE;

SELECT DatabaseName = d.name
    , SchemaName = s.name
    , ObjectName = o.name
    , AllocatedMB = COUNT(1) * 8192E0 / 1048576
    , PagesInMemory = COUNT(1)
FROM sys.dm_os_buffer_descriptors dobd
    INNER JOIN sys.allocation_units au 
        ON dobd.allocation_unit_id = au.allocation_unit_id
    INNER JOIN sys.partitions p 
        ON au.container_id = p.hobt_id 
             AND (au.type = 1 OR au.type = 0)
    INNER JOIN sys.objects o ON p.object_id = o.object_id
    INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
    INNER JOIN sys.databases d 
        ON dobd.database_id = d.database_id
WHERE d.name = 'TestReads'
    AND o.name = 'TestReads'
    AND o.type = 'U'
GROUP BY d.name
    , s.name
    , o.name;

A primeira instrução de seleção acima mostra que, de fato, a tabela consiste em 10.000 linhas, com 5.025 páginas no total, 5.020 páginas usadas e 5.000 páginas de dados; exatamente como seria de esperar:

insira a descrição da imagem aqui

A segunda instrução select confirma que não temos nada na memória para a TestReadstabela.

Em uma nova sessão , fazemos a seguinte consulta, anotando o session_id:

USE TestReads;

SET STATISTICS IO ON;

SELECT *
FROM dbo.TestReads;

Como seria de esperar, isso lê a tabela inteira do disco para a memória, conforme mostrado na saída de SET STATISTICS IO ON:

(10000 row(s) affected)
Table 'TestReads'. Scan count 1, logical reads 5020, physical reads 3, 
read-ahead reads 4998, lob logical reads 0, lob physical reads 0, lob 
read-ahead reads 0.

Em uma terceira sessão, inspecionamos sys.dm_exec_sessions:

SELECT des.session_id
    , des.reads
    , des.logical_reads
FROM sys.dm_exec_sessions des
WHERE des.session_id = 57; /* session_id from the 2nd (previous) session */

Eu esperaria ver sys.dm_exec_sessionsmostrar pelo menos 5.000 para ambos readse logical_reads. Infelizmente, eu vejo readsmostra zero. logical_readsmostra um número esperado de leituras em algum lugar ao norte de 5.000 - mostra 5.020 no meu teste:

insira a descrição da imagem aqui

Eu sei que o SQL Server leu a TestReadstabela inteira na memória, em virtude da sys_dm_os_buffer_descriptorsDMV:

USE TestReads;
GO
SELECT DatabaseName = d.name
    , SchemaName = s.name
    , ObjectName = o.name
    , AllocatedMB = COUNT(1) * 8192E0 / 1048576
    , PagesInMemory = COUNT(1)
FROM sys.dm_os_buffer_descriptors dobd
    INNER JOIN sys.allocation_units au 
        ON dobd.allocation_unit_id = au.allocation_unit_id
    INNER JOIN sys.partitions p 
        ON au.container_id = p.hobt_id 
            AND (au.type = 1 OR au.type = 0)
    INNER JOIN sys.objects o ON p.object_id = o.object_id
    INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
    INNER JOIN sys.databases d 
        ON dobd.database_id = d.database_id
WHERE d.name = 'TestReads'
    AND o.name = 'TestReads'
    AND o.type = 'U'
GROUP BY d.name
    , s.name
    , o.name;

insira a descrição da imagem aqui

O que estou fazendo de errado?

Estou usando o SQL Server 2012 11.0.5343 para este teste.


Outras descobertas:

Se eu executar o seguinte:

SELECT des.session_id
    , des.reads
    , des.logical_reads
FROM sys.dm_exec_sessions des

Eu vejo o reads784 na sessão em que estou criando o equipamento de teste; no entanto, todas as outras sessões mostram zero na readscoluna.

Atualizei agora minha instância de teste do SQL Server para 11.0.6020; no entanto, o resultado é o mesmo.

Max Vernon
fonte
sys.dm_exec_requestslhe dará quase o mesmo que os set statistics io onresultados.
Kin Shah
11
Interessante SET STATISTICS IO ONantes de ler a tabela da 2ª sessão relata 3 leituras físicas e 4998 leituras de leitura antecipada; no entanto, sys.dm_exec_sessionsainda não reflete isso na readscoluna.
Max Vernon
2
Em 2012, vejo frequentemente 0 para ambas as leituras e leituras lógicas, apesar dos resultados diferentes de zero relatados em STATISTICS IO i.stack.imgur.com/XbHae.png
Martin Smith -
11
Na verdade, vejo as duas colunas zero com a minha abordagem em todas as edições que testei de 2008 a SQL2016CTP3
Martin Smith
11
@ MartinSmith e Max: Eu também tenho visto um atraso em alguns dos incrementos dos readscampos. Eu suspeito que ele funciona muito como o session_space_usage ou qualquer DMV que mostre o uso do tempdb por sessão que não é incrementado até que a "solicitação" seja concluída.
Solomon Rutzky

Respostas:

2

Meu entendimento sempre foi que readsé apenas físico (ou seja, do disco) e logical_readsé apenas do Buffer Pool (ou seja, da memória). Fiz um teste rápido com uma tabela menor, com apenas 2 páginas de dados e 3 páginas no total, e o que estou vendo parece confirmar essas duas definições.

Uma coisa que provavelmente está lhe dando maus resultados é que você não está limpando a memória. Você deve executar o seguinte entre testes para forçar o recarregamento do disco:

DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;

Minha configuração de teste foi a seguinte:

CREATE TABLE dbo.ReadTest (Col1 CHAR(7500) DEFAULT (' '));
INSERT INTO dbo.ReadTest (Col1) VALUES (DEFAULT), (DEFAULT);

Em seguida, executei o seguinte:

SELECT reads, logical_reads FROM sys.dm_exec_sessions WHERE session_id = @@SPID;
SELECT * FROM dbo.ReadTest;

(Sim, eu estava testando na mesma sessão em que estava executando o DMV, mas isso não distorceu os resultados para o readscampo e, se nada mais, era pelo menos consistente se contribuísse para o logical_readscampo.)

Para testar, eu executaria o comando DBCC e as duas consultas SELECT. Então eu veria um salto nos campos readse logical_reads. Eu executava as consultas SELECT novamente e, às vezes, via um salto adicional reads.

Depois disso, eu executava as duas consultas SELECT várias vezes e as readspermaneceriam as mesmas, enquanto as logical_readsquatro aumentavam todas as vezes.

Eu começaria novamente executando o DBCC e veria o mesmo padrão. Fiz isso várias vezes e os números relatados foram consistentes em todas as execuções de teste.


Mais informações:

Também estou testando no SQL Server 2012, SP2 - 64 bits (11.0.5343).

Os seguintes comandos do DBCC tentamos e não vimos nenhum efeito:

DBCC FREESYSTEMCACHE('ALL');
DBCC FREEPROCCACHE;
DBCC FREESESSIONCACHE;

Na maioria das vezes DBCC DROPCLEANBUFFERSfunciona, mas ocasionalmente vejo que ele ainda está no Buffer Pool. Ímpar.

Quando eu:

  • DBCC DROPCLEANBUFFERS: As leituras aumentam 24 e as lições_lógicas aumentam 52.
  • Executar SELECT [Col1] FROM dbo.ReadTest;novamente: as leituras não aumentam, mas as lições_lógicas aumentam 6.
  • Adicione um espaço ao texto da consulta e execute novamente: as leituras não aumentam, mas as lições_lógicas aumentam 52 (exatamente como logo após DBCC DROPCLEANBUFFERS).

Parece que as 52 leituras lógicas são responsáveis ​​pela geração do plano e pelos resultados, o que implica que a geração do plano causou 46 leituras lógicas adicionais. Mas as leituras físicas não sobem novamente e, no entanto, são as mesmas 52 leituras lógicas que eram quando precisavam também fazer as leituras físicas, portanto logical_reads, não incluem as físicas reads. Estou apenas esclarecendo esse ponto, esteja ele sendo ou não declarado ou implícito na questão.

MAS, um comportamento que notei que se destaca (pelo menos um pouco) usando a existência das páginas de dados da tabela sys.dm_os_buffer_descriptors: ela é recarregada por algum outro processo. Se você DROPCLEANBUFFERS e verificar imediatamente, ele deve ter desaparecido. Mas espere alguns minutos e ele aparecerá novamente, mas desta vez sem todas as páginas de dados. No meu teste, a tabela possui 1 página IAM e 4 páginas de dados. Todas as 5 páginas estão no buffer pool depois que eu faço o SELECT. Mas quando é recarregado por algum outro processo, é apenas a página do IAM e a 1 página de dados. Eu pensei que poderia ser o SSMS IntelliSense, mas removi todas as referências a esse nome de objeto na minha guia de consulta e ele ainda é recarregado.

Solomon Rutzky
fonte
engraçado, removi o DBCC DROPCLEANBUFFERS(e outros DBCC DROPxxxcomandos) do meu equipamento de teste porque eles não fizeram nenhuma diferença. Definir o banco de dados offline descarta todos os buffers e tudo o mais associado ao banco de dados.
Max Vernon
Eu estava com o mesmo entendimento que você sobre as leituras serem físicas e leituras lógicas começam no buffer pool, btw.
Max Vernon
Eu também tentei com: DBCC FREESYSTEMCACHE ('ALL'); DBCC FREEPROCCACHE; DBCC FREESESSIONCACHE;
Max Vernon
11
@MaxVernon Poderia ser a "mantê-los guessin" recurso ;-)
Solomon Rutzky
2
@ MaxVernon, não se esqueça de executar um CHECKPOUNTno contexto do banco de dados antes DBCC DROPCLEANBUFFERS.
Dan Guzman