{"id":1680,"date":"2020-10-28T03:41:45","date_gmt":"2020-10-28T06:41:45","guid":{"rendered":"https:\/\/thesqltimes.com\/blog\/?p=1680"},"modified":"2020-10-28T03:43:59","modified_gmt":"2020-10-28T06:43:59","slug":"sqlserver-logical-reads","status":"publish","type":"post","link":"https:\/\/thesqltimes.com\/blog\/2020\/10\/28\/sqlserver-logical-reads\/","title":{"rendered":"Cuidados ao interpretar o Logical Reads no SQL Server"},"content":{"rendered":"<div class=\"pld-like-dislike-wrap pld-template-1\">\r\n    <div class=\"pld-like-wrap  pld-common-wrap\">\r\n    <a href=\"javascript:void(0)\" class=\"pld-like-trigger pld-like-dislike-trigger  \" title=\"Muito \u00fatil!\" data-post-id=\"1680\" data-trigger-type=\"like\" data-restriction=\"cookie\" data-already-liked=\"0\">\r\n                        <i class=\"fas fa-thumbs-up\"><\/i>\r\n                <\/a>\r\n    <span class=\"pld-like-count-wrap pld-count-wrap\">    <\/span>\r\n<\/div><\/div><span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Tempo de Leitura:<\/span> <span class=\"rt-time\"> 8<\/span> <span class=\"rt-label rt-postfix\">minutos<\/span><\/span><p>Recentemente, me deparei com um caso curioso na Power Tuning: O <a href=\"https:\/\/www.linkedin.com\/in\/eduardo-rabelo\/\">Eduardo<\/a> identificou uma s\u00e9rie de bloqueios causados por uma query rodando em uma base com alguns TB de tamanho. Eu aprovetei que estava conectado no ambiente e fui dar uma olhada na query.<\/p>\n<p>Duas coisas me chamaram a aten\u00e7\u00e3o:<\/p>\n<ol>\n<li>A primeira foi esse n\u00famero de reads&#8230; Acho que nunca vi passar da casa do milh\u00e3o, chegando ao bilh\u00e3o. N\u00famero foi t\u00e3o grande que at\u00e9 confundi com trilh\u00e3o rs (<a href=\"https:\/\/twitter.com\/fabianoDBA\/status\/1321276700228014080\">thanks @fabianoDBA pela corre\u00e7\u00e3o<\/a>)\n<ol>\n<li>Convertendo esse n\u00famero de paginas para GB, voc\u00ea checa em 7TB ( GB = Paginas\/131072)<img decoding=\"async\" src=\"https:\/\/pbs.twimg.com\/media\/ElXsFPtXgAQcLEm?format=jpg&amp;name=small\" alt=\"Image\" \/><\/li>\n<\/ol>\n<\/li>\n<li>A outra coisa interessante foi o fato de que essa a tabela envolvida tem apenas 1TB de tamanho e somando os \u00edndices nela, 2TB<\/li>\n<\/ol>\n<p>Ent\u00e3o, como \u00e9 poss\u00edvel um request ler mais de 7TB de dados, sendo que a tabela n\u00e3o tem nem metade disso? A resposta \u00e9 muito simples: O Logical Reads que voc\u00ea v\u00ea em diversos lugares conta leituras repetidas!<\/p>\n<p>A <a href=\"https:\/\/github.com\/amachanic\/sp_whoisactive\">sp_WhoIsActive<\/a> (de onde eu tirei esses prints acima) consulta essa informa\u00e7\u00e3o de reads da coluna logical_reads da sys.dm_exec_requests. <a href=\"https:\/\/docs.microsoft.com\/en-us\/sql\/relational-databases\/system-dynamic-management-views\/sys-dm-exec-requests-transact-sql?view=sql-server-ver15\">Olhando a documenta\u00e7\u00e3o<\/a>:<\/p>\n<p id=\"pirgarw\"><img loading=\"lazy\" decoding=\"async\" width=\"1430\" height=\"82\" class=\"alignnone size-full wp-image-1681 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f37cd18ae.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f37cd18ae.png 1430w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f37cd18ae-300x17.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f37cd18ae-1024x59.png 1024w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f37cd18ae-768x44.png 768w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p>Voc\u00ea deve ter aprendido em diversos lugares sobre esse assunto que &#8220;Logical Reads&#8221; significa que a query leu da mem\u00f3ria, em contraste com o &#8220;Physical Reads&#8221;, que \u00e9 quando ele contabilizada as p\u00e1ginas lidas do disco. Por\u00e9m, o que n\u00e3o fica claro a\u00ed \u00e9 que leituras repetidas contam mais de uma vez nesse contador. Aqui est\u00e1 um exemplo simple de observar. Primeiro crie uma tabela com uma \u00fanica p\u00e1gina:<\/p>\n<pre class=\"lang:tsql decode:true\" title=\"Criando uma tabela com 1 p\u00e1gina de dados\">CREATE TABLE SinglePage(c char(7000));\nINSERT INTO SinglePage VALUES('TheSqlTimes');\n\n-- Confirme que tem 1 p\u00e1gina de dados em uso\nSELECT data_pages FROM sys.allocation_units AU WHERE AU.container_id IN (\n    SELECT partition_id FROM sys.partitions WHERE object_id = OBJECT_ID('SinglePage')\n)\n\n-- Confirme que o select l\u00ea 1 p\u00e1gina...\nSET STATISTICS TIME,IO ON\nSELECT * FROM SinglePage\nSET STATISTICS TIME,IO OFF<\/pre>\n<p id=\"yyBOPQK\"><img loading=\"lazy\" decoding=\"async\" width=\"1119\" height=\"208\" class=\"alignnone size-full wp-image-1682 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f7e0583ca.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f7e0583ca.png 1119w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f7e0583ca-300x56.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f7e0583ca-1024x190.png 1024w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f7e0583ca-768x143.png 768w\" sizes=\"auto, (max-width: 1119px) 100vw, 1119px\" \/><\/p>\n<p id=\"yTUVvoy\"><img loading=\"lazy\" decoding=\"async\" width=\"966\" height=\"373\" class=\"alignnone size-full wp-image-1683 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f7f457138.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f7f457138.png 966w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f7f457138-300x116.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f7f457138-768x297.png 768w\" sizes=\"auto, (max-width: 966px) 100vw, 966px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>Agora vamos tornar as coisas mais interessantes, neste pr\u00f3ximo exemplo eu vou criar uma outra tabela com 5000 linhas e fazer um CROSS APPLY com a tabela de 1 p\u00e1gina:<\/p>\n<pre class=\"lang:tsql decode:true\" title=\"Cross apply\">-- Agora, observe o resultado disso...\nSELECT TOP 5000 1 AS SomeCol INTO #Rows FROM sys.all_objects a1,sys.all_objects a2;\n\nSET STATISTICS TIME,IO ON\nSELECT\n    COUNT(*)\nFROM\n    #Rows R\n    CROSS APPLY (\n        SELECT TOP 1 c FROM SinglePage WHERE LEN(c) &gt; R.SomeCol\n    ) C\nSET STATISTICS TIME,IO OFF<\/pre>\n<p>E se voc\u00ea olhar na aba de mensagens, ver\u00e1 uma coisa bem interessante:<\/p>\n<p id=\"YJRvrXy\"><img loading=\"lazy\" decoding=\"async\" width=\"1071\" height=\"325\" class=\"alignnone size-full wp-image-1684 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f87e3e0c8.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f87e3e0c8.png 1071w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f87e3e0c8-300x91.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f87e3e0c8-1024x311.png 1024w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f87e3e0c8-768x233.png 768w\" sizes=\"auto, (max-width: 1071px) 100vw, 1071px\" \/><\/p>\n<p>Ora, como assim a tabela SinglePage, que tem apenas 1 p\u00e1gina, possui 5000 leituras l\u00f3gicas? E a resposta eu j\u00e1 disse no in\u00edcio do post. Se voc\u00ea olhar o plano dessa query simples, vai notar<\/p>\n<p id=\"ljjCZuC\"><img loading=\"lazy\" decoding=\"async\" width=\"948\" height=\"288\" class=\"alignnone size-full wp-image-1685 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f8ec97210.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f8ec97210.png 948w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f8ec97210-300x91.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98f8ec97210-768x233.png 768w\" sizes=\"auto, (max-width: 948px) 100vw, 948px\" \/><br \/>\n<em>(PS.: thanks <a href=\"https:\/\/blogfabiano.com\/\">Fabiano Amorim<\/a> por me deixar copiar sua ideia de mostrar como as linhas fluem de um operador pra outro, sem que eu te pergunte se posso fazer isso, rs)<\/em><\/p>\n<p>Para cada linha de #Rows, ele vai ler\u00a0 a \u00fanica p\u00e1gina l\u00e1 da tabela SinglePage&#8230; Ou seja, para cada uma das 5000 linhas de #Rows, ele leu 1 p\u00e1gina de SinglePage, totalizando 5000 leituras l\u00f3gicas em SinglePage. A mesma p\u00e1gina, lida 5000 vezes.<br \/>\nOu seja, se voc\u00ea ver um n\u00famero de 5000 logical reads, ele pode significar tr\u00eas coisas:<\/p>\n<ul>\n<li>Ou a sua query realmente leu 5000 p\u00e1ginas diferentes<\/li>\n<li>Ou a sua query leu 1 p\u00e1gina 5000 vezes<\/li>\n<li>Ou a sua query leu algumas p\u00e1ginas repetidas, algumas uma vez, etc.\n<ul>\n<li>Pode ter lido uma 4999 vezes, e a ultima 1 unica vez&#8230;<\/li>\n<li>Ou metade\/metade, 2500 uma, 2500 outra e por a\u00ed vai&#8230;<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>\u00c9 isso a\u00ed mesmo&#8230; N\u00e3o d\u00e1 pra saber s\u00f3 olhando os n\u00fameros. Ent\u00e3o, se voc\u00ea ver 1TB de Logical Reads, n\u00e3o quer dizer que ele afundou seu BUFFER POOL, se matando para jogar 1TB entre a mem\u00f3ria e o disco (<strong>mas pode ter sido isso<\/strong>). Apesar disso, esse n\u00famero n\u00e3o perde sua relev\u00e2ncia. Muito pelo contr\u00e1rio: O fato dele contar repetida leituras na mesma p\u00e1gina pode n\u00e3o indicar revelar a mem\u00f3ria usada, mas indica outra coisa muito importante: O consumo de CPU! Afinal, mesmo sendo repetido e gastando a mesma mem\u00f3ria, se sua query faz mais logical reads, significa que ela gastou mais CPU pra ter esse trabalho. Afinal, Ler 1 p\u00e1gina 1 vez gasta menos CPU do que l\u00ea a mesma p\u00e1gina 10 vezes, ou 1 milh\u00e3o de vezes.<\/p>\n<p>E com isso voltamos ao caso original. O plano da query em execu\u00e7\u00e3o era este:<\/p>\n<p id=\"KxzhXAT\"><img loading=\"lazy\" decoding=\"async\" width=\"1082\" height=\"503\" class=\"alignnone size-full wp-image-1686 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fb31a33fc.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fb31a33fc.png 1082w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fb31a33fc-300x139.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fb31a33fc-1024x476.png 1024w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fb31a33fc-768x357.png 768w\" sizes=\"auto, (max-width: 1082px) 100vw, 1082px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>Reparem nos nested loops envolvidos&#8230; Para n\u00e3o expor o ambiente onde isso foi feito, eu vou reproduzir esse problema na<a href=\"https:\/\/www.brentozar.com\/archive\/2015\/10\/how-to-download-the-stack-overflow-database-via-bittorrent\/\"> minha Base do StackOverflow<\/a>.<br \/>\nA query era semelhante a esta:<\/p>\n<pre class=\"lang:tsql decode:true \">SELECT\n    MIN(CreationDate),MAX(CreationDate)\nFROM\n    Posts\nWHERE\n    AnswerCount = 987\nOPTION(RECOMPILE)<\/pre>\n<p>Este \u00e9 o plano estimado (reparem que \u00e9 igual ao do ambiente real que mostrei):<\/p>\n<p id=\"ffxvIXg\"><img loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"380\" class=\"alignnone size-full wp-image-1687 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fb810ab47.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fb810ab47.png 960w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fb810ab47-300x119.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fb810ab47-768x304.png 768w\" sizes=\"auto, (max-width: 960px) 100vw, 960px\" \/><\/p>\n<p>A tabela Posts possui 3729195 linhas e 2 \u00edndices. O Cluster (a PK) e um \u00edndice na coluna de data (IxCreationDate). O \u00edndice, que voc\u00ea na imagem acima, possui 5454\u00a0 p\u00e1ginas, ~ 42MB ( MB = Paginas\/128 ):<\/p>\n<figure id=\"attachment_1690\" aria-describedby=\"caption-attachment-1690\" style=\"width: 1146px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-1690 size-full\" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fd1405ef0.png\" alt=\"\" width=\"1146\" height=\"235\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fd1405ef0.png 1146w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fd1405ef0-300x62.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fd1405ef0-1024x210.png 1024w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fd1405ef0-768x157.png 768w\" sizes=\"auto, (max-width: 1146px) 100vw, 1146px\" \/><figcaption id=\"caption-attachment-1690\" class=\"wp-caption-text\">diferente do exemplo acima, aqui eu uso used_pages, pois no exemplo acima era um HEAP, aqui \u00e9 um \u00edndice e existem algumas p\u00e1ginas a mais que quero colocar na conta. Mas, isso n\u00e3o muda nossa linha de racioc\u00ednio&#8230;<\/figcaption><\/figure>\n<p id=\"ZixwfFP\"><img loading=\"lazy\" decoding=\"async\" width=\"792\" height=\"268\" class=\"alignnone size-full wp-image-1689 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fce5a8009.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fce5a8009.png 792w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fce5a8009-300x102.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fce5a8009-768x260.png 768w\" sizes=\"auto, (max-width: 792px) 100vw, 792px\" \/><\/p>\n<p>Rodando a query acima, reparem o n\u00famero de leituras na sp_WhoIsActive e sys.dm_exec_requests<\/p>\n<p id=\"kGJzvJf\"><img loading=\"lazy\" decoding=\"async\" width=\"957\" height=\"277\" class=\"alignnone size-full wp-image-1692 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fe343cde0.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fe343cde0.png 957w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fe343cde0-300x87.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fe343cde0-768x222.png 768w\" sizes=\"auto, (max-width: 957px) 100vw, 957px\" \/><\/p>\n<p>Mais de 18 milh\u00f5es de leituras, em uma tabela que possui apenas 5000 p\u00e1ginas no \u00edndice em que se est\u00e1 fazendo o index scan! Mais de 138GB pra uma tabela de 42MB!<br \/>\nE, como voc\u00ea aprendeu acima, n\u00e3o quer dizer, necessriamente, que foram carregados\u00a0 138GB para a mem\u00f3ria, mas sim, provavelmente um n\u00famero repetido de leituras. E olhando no plano \u00e9 f\u00e1cil entender o porqu\u00ea:<\/p>\n<p id=\"VcyBGNB\"><img loading=\"lazy\" decoding=\"async\" width=\"1048\" height=\"514\" class=\"alignnone size-full wp-image-1693 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fec1318af.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fec1318af.png 1048w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fec1318af-300x147.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fec1318af-1024x502.png 1024w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98fec1318af-768x377.png 768w\" sizes=\"auto, (max-width: 1048px) 100vw, 1048px\" \/><\/p>\n<p>O SQL resolveu dividir o c\u00e1lculo do MIN e MAX em duas etapas.\u00a0 A parte mais superior faz o MIN e o que vou explicar a seguir vale para a parte inferior, que \u00e9 o MAX. <a href=\"https:\/\/www.brentozar.com\/pastetheplan\/?id=Byldb5IOP\">Se quiser o plano, veja aqui<\/a>.<\/p>\n<p id=\"PbxPoci\"><img loading=\"lazy\" decoding=\"async\" width=\"959\" height=\"287\" class=\"alignnone size-full wp-image-1694 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98ff20be3f5.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98ff20be3f5.png 959w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98ff20be3f5-300x90.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98ff20be3f5-768x230.png 768w\" sizes=\"auto, (max-width: 959px) 100vw, 959px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>Como temos um \u00edndice na tabela e estamos querendo o MIN, o SQL colocou um TOP ali no meio para pedir ao Storage Engine que fizesse o scan ordenado pelo \u00edndice.<\/p>\n<p id=\"jYsnjSl\"><img loading=\"lazy\" decoding=\"async\" width=\"962\" height=\"418\" class=\"alignnone size-full wp-image-1695 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98ffa03a9ff.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98ffa03a9ff.png 962w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98ffa03a9ff-300x130.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f98ffa03a9ff-768x334.png 768w\" sizes=\"auto, (max-width: 962px) 100vw, 962px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>Ou seja o que ele pensou foi: Ora, se o Rodrigo quer\u00a0 a menor data onde AnswerCount\u00a0 = 987, ent\u00e3o eu vou come\u00e7ar da menor data do \u00edndice e vou l\u00e1 na tabela checar se AnswerCount = 987<br \/>\nAqui est\u00e1 o que ele faz no lookup:<\/p>\n<p id=\"BFGokaN\"><img loading=\"lazy\" decoding=\"async\" width=\"579\" height=\"561\" class=\"alignnone size-full wp-image-1696 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f99000e597b3.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f99000e597b3.png 579w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f99000e597b3-300x291.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f99000e597b3-16x16.png 16w\" sizes=\"auto, (max-width: 579px) 100vw, 579px\" \/><\/p>\n<p>Inteligente, n\u00e3o? \u00c9&#8230; se fosse comum eu ter AnswerCount = 987&#8230; O problema \u00e9 que, e se a primeira data da tabela n\u00e3o tem AnswerCount\u00a0 = 987? Ent\u00e3o n\u00e3o tem match e ele tem quer ir pro pr\u00f3ximo&#8230; E se o pr\u00f3ximo n\u00e3o bater nesse filtro?\u00a0 Tenta a pr\u00f3xima&#8230;<br \/>\nE se a primeira linha com AnswerCount = 987 ficar l\u00e1 no meio das datas? Isso quer dizer que essa decis\u00e3o do SQL fez ele passar por uma infinidade de linhas, para s\u00f3 ent\u00e3o chegar na que satisfaz o filtro.. E ai est\u00e1 o porqu\u00ea ele leu tantas p\u00e1ginas&#8230; At\u00e9 ele achar a menor data, ele teve que andar bastante. Al\u00e9m disso, para cada um dessas linhas lidas, ele precisou ir no \u00edndice cluster&#8230; <strong>Mais pelo menos 4 p\u00e1ginas POR LINHA (pois o cluster tem 4 n\u00edveis e h\u00e1 ainda mais algumas leituras que ficam de fora do escopo desse posts&#8230; Mas aqui inclusive, ele est\u00e1 lendo algumas p\u00e1ginas do \u00edndice e de controle repetidas vezes devido ao lookup)! &#8230;<\/strong><\/p>\n<p id=\"qcfmBgU\"><img loading=\"lazy\" decoding=\"async\" width=\"518\" height=\"194\" class=\"alignnone size-full wp-image-1697 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f9900e32c1de.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f9900e32c1de.png 518w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f9900e32c1de-300x112.png 300w\" sizes=\"auto, (max-width: 518px) 100vw, 518px\" \/><\/p>\n<p><strong>S\u00f3 da primeira parte, considerando pelo menos 4 p\u00e1ginas, temos 7 milh\u00f5es de reads ( 1864703 * 4 ). A segunda parte gastou um n\u00famero parecido, ent\u00e3o, s\u00f3 nessa conta simples, voc\u00ea tem 14 milh\u00f5es.<\/strong><\/p>\n<p>O n\u00famero de leituras final foi:<\/p>\n<p id=\"nBxnoGO\"><img loading=\"lazy\" decoding=\"async\" width=\"722\" height=\"421\" class=\"alignnone size-full wp-image-1699 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f9902a60e9f7.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f9902a60e9f7.png 722w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f9902a60e9f7-300x175.png 300w\" sizes=\"auto, (max-width: 722px) 100vw, 722px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>Mais de 20 milh\u00f5es de logical reads, em uma tabela que somando todos os \u00edndices temos 871145 p\u00e1ginas&#8230;<\/p>\n<p id=\"xRfeRHL\"><img loading=\"lazy\" decoding=\"async\" width=\"1045\" height=\"207\" class=\"alignnone size-full wp-image-1700 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f9902f65f062.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f9902f65f062.png 1045w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f9902f65f062-300x59.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f9902f65f062-1024x203.png 1024w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f9902f65f062-768x152.png 768w\" sizes=\"auto, (max-width: 1045px) 100vw, 1045px\" \/><\/p>\n<p>O SQL achou que iria encontrar rapidamente uma linha, mas n\u00e3o (eu propositalmente coloquei as linhas com AnswerCount = 987 exatamente no meio das datas). Tanto para buscar o m\u00e1ximo e o m\u00ednimo, ele tomou a decis\u00e3o errada.<\/p>\n<p>At\u00e9 um scan, sem paralelismo, no \u00edndice cluster, teria sido menos caro (e mais r\u00e1pido):<\/p>\n<p id=\"DxBvjvO\"><img loading=\"lazy\" decoding=\"async\" width=\"591\" height=\"402\" class=\"alignnone size-full wp-image-1701 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f99032f75b2a.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f99032f75b2a.png 591w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f99032f75b2a-300x204.png 300w\" sizes=\"auto, (max-width: 591px) 100vw, 591px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>E olha s\u00f3 que interessante: Eu dropei o \u00edndice o cluster e refiz a query&#8230; Dessa vez o plano gerado foi com RID lookup (ao inv\u00e9s do Key Lookup):<\/p>\n<p id=\"tyRSYeL\"><img loading=\"lazy\" decoding=\"async\" width=\"936\" height=\"524\" class=\"alignnone size-full wp-image-1703 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990e34150b0.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990e34150b0.png 936w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990e34150b0-300x168.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990e34150b0-768x430.png 768w\" sizes=\"auto, (max-width: 936px) 100vw, 936px\" \/><\/p>\n<p>Curiosamente, a query leu muito menos p\u00e1ginas:<\/p>\n<p id=\"wnfBYBy\"><img loading=\"lazy\" decoding=\"async\" width=\"618\" height=\"495\" class=\"alignnone size-full wp-image-1704 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990e58a81ae.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990e58a81ae.png 618w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990e58a81ae-300x240.png 300w\" sizes=\"auto, (max-width: 618px) 100vw, 618px\" \/><\/p>\n<p>E isso \u00e9 completamente esperado: Com o Key lookup, o SQL precisa usar a estrutura do \u00edndice, que come\u00e7a da p\u00e1gina root e vai descendo a \u00e1rvore bin\u00e1ria at\u00e9 chegar na p\u00e1gina do n\u00edvel folha do cluster. Sem o cluster, nos \u00edndices NONCLUSTERED, o SQL armazena um ponteiro diretamente para a p\u00e1gina e slot da linha, em outras palavras, o SQL l\u00ea muito menos p\u00e1ginas para cada uma das linhas retornadas no \u00edndice ixCreationDate. Somando o total de linhas da primeira com o total de linhas da segunda, chegamos bem pr\u00f3ximo do valor de p\u00e1ginas lidos:<\/p>\n<p>Parte do MIN:<\/p>\n<p id=\"oWYPAdD\"><img loading=\"lazy\" decoding=\"async\" width=\"618\" height=\"355\" class=\"alignnone size-full wp-image-1707 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990f662f50b.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990f662f50b.png 618w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990f662f50b-300x172.png 300w\" sizes=\"auto, (max-width: 618px) 100vw, 618px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>Parte do MAX:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"560\" height=\"246\" class=\"alignnone size-full wp-image-1706 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990f547e5e8.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990f547e5e8.png 560w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990f547e5e8-300x132.png 300w\" sizes=\"auto, (max-width: 560px) 100vw, 560px\" \/><\/p>\n<p id=\"VgpJwAl\"><img loading=\"lazy\" decoding=\"async\" width=\"456\" height=\"179\" class=\"alignnone size-full wp-image-1708 \" src=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990fa425f02.png\" alt=\"\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990fa425f02.png 456w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/10\/img_5f990fa425f02-300x118.png 300w\" sizes=\"auto, (max-width: 456px) 100vw, 456px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>As 7000 p\u00e1ginas de diferen\u00e7a podem ser da leitura do \u00edndice IxCreationDate duas vezes&#8230;<\/p>\n<p>&nbsp;<\/p>\n<p>Para otimizar esta query, eu teria v\u00e1rias op\u00e7\u00f5es:<\/p>\n<ul>\n<li>For\u00e7ar o scan<\/li>\n<li>Criar um \u00edndice em AnswerCount e CreationDate<\/li>\n<li>Verificar se as estat\u00edsticas contribuiram para um p\u00e9ssima estimativa e pensar em um sample melhor<\/li>\n<li>Avaliar se aqui \u00e9 um problema de Row Goal (procurem o Fabiano que ele tem muito assunto sobre isso)<\/li>\n<\/ul>\n<p>Enfim, v\u00e1rias abordagens, cada uma dependendo do cen\u00e1rio.\u00a0 O que fica de aprendizado ent\u00e3o deste post?<\/p>\n<ul>\n<li>Logical reads conta leituras repetidas na mesma p\u00e1gina<\/li>\n<li>Logical reads te fala mais sobre o consumo de CPU do que de mem\u00f3ria (n\u00e3o em todos os casos)<\/li>\n<li>N\u00e3o falei isso durante o post mas fica o aviso: Cuidado com as interpreta\u00e7\u00f5es<br \/>\nSe seu request est\u00e1 executando mais de um comando, em loop por exemplo, o logical reads vai ter a soma de todos os comandos e todas as tabelas envolvidas.<br \/>\nNo meu caso, eu vi que era somente 1 comando com 1 tabela (por isso achei estranho)<\/li>\n<li>Dica: P\u00e1ginas\/131072 = ValorGB<\/li>\n<li>Dica: P\u00e1ginas\/128 = MB<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>At\u00e9 a pr\u00f3xima!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Recentemente, me deparei com um caso curioso na Power Tuning: O Eduardo identificou uma s\u00e9rie de bloqueios causados por uma query rodando em uma base com alguns TB de tamanho. Eu aprovetei que estava conectado no ambiente e fui dar uma olhada na query. Duas coisas me chamaram a aten\u00e7\u00e3o: A primeira foi esse n\u00famero&hellip;&nbsp;<a href=\"https:\/\/thesqltimes.com\/blog\/2020\/10\/28\/sqlserver-logical-reads\/\" rel=\"bookmark\"><span class=\"screen-reader-text\">Cuidados ao interpretar o Logical Reads no SQL Server<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":1702,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_exactmetrics_skip_tracking":false,"_exactmetrics_sitenote_active":false,"_exactmetrics_sitenote_note":"","_exactmetrics_sitenote_category":0,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"neve_meta_sidebar":"","neve_meta_container":"","neve_meta_enable_content_width":"","neve_meta_content_width":0,"neve_meta_title_alignment":"","neve_meta_author_avatar":"","neve_post_elements_order":"","neve_meta_disable_header":"","neve_meta_disable_footer":"","neve_meta_disable_title":"","footnotes":""},"categories":[296,7],"tags":[40,364,367,365,373,369,368,366,372,374,371,73,370],"series":[],"class_list":["post-1680","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-internals","category-sql-server","tag-indices","tag-key-lookup","tag-leituras","tag-logical-reads","tag-nested-loops","tag-pages","tag-paginas","tag-reads","tag-rid-lookup","tag-scan","tag-sp_whoisactive","tag-sql-server","tag-sys-dm_exec_requests"],"_links":{"self":[{"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/posts\/1680","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/comments?post=1680"}],"version-history":[{"count":3,"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/posts\/1680\/revisions"}],"predecessor-version":[{"id":1710,"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/posts\/1680\/revisions\/1710"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/media\/1702"}],"wp:attachment":[{"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/media?parent=1680"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/categories?post=1680"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/tags?post=1680"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/series?post=1680"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}