{"id":1398,"date":"2020-04-12T01:00:10","date_gmt":"2020-04-12T04:00:10","guid":{"rendered":"http:\/\/thesqltimes.com\/blog\/?p=1398"},"modified":"2020-04-27T09:12:44","modified_gmt":"2020-04-27T12:12:44","slug":"recuperar-delete-sem-backup-full-2","status":"publish","type":"post","link":"https:\/\/thesqltimes.com\/blog\/2020\/04\/12\/recuperar-delete-sem-backup-full-2\/","title":{"rendered":"Recuperando dados deletados do SQL Server sem Backup Full &#8211; Parte 2"},"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=\"1398\" 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><div class=\"seriesmeta\">Post 2\/6. Este post \u00e9 parte da s\u00e9rie: <a href=\"https:\/\/thesqltimes.com\/blog\/series\/recuperando-dados-deletados-sem-backup-full\/\" class=\"series-294\" title=\"Recuperando dados deletados sem Backup Full\">Recuperando dados deletados sem Backup Full<\/a>\r\n<\/div>\r\n<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\"> 4<\/span> <span class=\"rt-label rt-postfix\">minutos<\/span><\/span><p>Ol\u00e1! Este \u00e9 mais um post da s\u00e9rie que tem o intuito de mostrar uma, das v\u00e1rias maneiras, que voc\u00ea pode tentar para recuperar dados que voc\u00ea deletou do seu SQL Server e n\u00e3o tem o famoso backup FULL!<\/p>\n<p>No \u00faltimo e primeiro post da s\u00e9rie eu criei um cen\u00e1rio de exemplo e apresentei a fun\u00e7\u00e3o: <strong>fn_dump_dblog<\/strong>! Como um dos meus objetivos com este blog \u00e9 explicar os porqu\u00eas, e n\u00e3o apenas dar solu\u00e7\u00f5es prontas, vou mostrar mais alguns detalhes desta fun\u00e7\u00e3o para que voc\u00ea entenda um pouco mais como ela ajuda nesta hist\u00f3ria e vamos utilizar ela para encontrar nossos registros deletados! Vamos nessa!<\/p>\n<p>A Barreira de ler o backup do log foi superada e agora \u00e9 hora de achar nosso DELETE.\u00a0 Existe tanta informa\u00e7\u00e3o na<strong> fn_dump_dblog<\/strong>, que eu precisaria de um s\u00e9rie de posts para explicar cada coluna, mas estou assumindo que voc\u00ea j\u00e1 fez o dever de casa e tem algum conhecimento internals sobre isso.<\/p>\n<p>Das colunas que ela retorna, as que nos interessa s\u00e3o estas:<\/p>\n<ul>\n<li><strong>Current LSN<\/strong><br \/>\n\u00c9 o LSN (Log Sequence Number) daquele registro no log. Na verdade, nem precisamos dela. Mas gosto de deix\u00e1-la apenas porque isso seria como a chave prim\u00e1ria desta tabela.<\/li>\n<li><strong>Operation<\/strong><br \/>\nIndica a opera\u00e7\u00e3o a que esse registro se refere. Geralmente come\u00e7a com <strong>&#8220;LOP_&#8221;<\/strong> + <strong>alguma coisa que podemos deduzir o que significa<\/strong>. LOP provavelmente vem de &#8220;<strong>L<\/strong>og <strong>OP<\/strong>eration&#8221;. Nada confirmado, isto \u00e9 s\u00f3 um <em>achismo<\/em> meu.\u00a0 No nosso caso, o que estamos procurando \u00e9 pela operation <strong>LOP_DELETE_ROWS (advinha o que signfica?)<br \/>\n<\/strong><\/li>\n<li><strong>Context<\/strong><br \/>\nPelo nome, essa nos d\u00e1 um pouco mais de detalhes de onde a opera\u00e7\u00e3o que foi feita, ou as circunst\u00e2ncias. Neste caso de um DELETE de uma tabela com a chave prim\u00e1ria criada como um \u00edndice cluster, estamos interessados em LCX_MARK_AS_GHOST e LCX_CLUSTERED , que pelos nomes indicam se o delete foi feito, de fato, removendo os registro da tabela, ou apenas <a href=\"https:\/\/docs.microsoft.com\/en-us\/sql\/relational-databases\/ghost-record-cleanup-process-guide?view=sql-server-ver15\">marcando como um ghost record.<\/a> E de novo, um <em>achismo<\/em> meu: &#8220;LCX&#8221; parece vir de &#8220;<strong>L<\/strong>og <strong>C<\/strong>onte<strong>X<\/strong>t&#8221;.<\/li>\n<li><strong>AllocUnitId<\/strong><br \/>\nUma tabela pode ter um ou mais allocation units, dependendo do particionamento, tipo de dados, \u00edndices, etc. Este \u00e9 o campo que vamos usar para achar somente os registros da tabela que queremos. Para encontrar o Allocation Unit da sua tabela:<\/p>\n<pre class=\"lang:tsql decode:true \" title=\"Descobrindo os allocations units para recupera\u00e7\u00e3o de DELETE\">SELECT\n    AU.allocation_unit_id\nFROM\n    sys.partitions P\n    JOIN\n    sys.allocation_units AU ON AU.container_id = P.hobt_id\nWHERE\n    P.object_id = OBJECT_ID('dbo.Cliente') AND P.index_id &lt;= 1<\/pre>\n<\/li>\n<li><strong>RowLog Contents 0<\/strong><br \/>\nEste \u00e9 a nossa linha, registro, seja l\u00e1 como voc\u00ea chama a\u00ed. \u00c9 aqui que <strong>est\u00e3o todos os valores das colunas<\/strong>, por\u00e9m, em formato bin\u00e1rio! Por isso, ele inicia com 0x, que \u00e9 um indicativo da representa\u00e7\u00e3o hexadecimal de um tipo de dados bin\u00e1rios. E lembre-se, que a cada dois d\u00edgitos hexadecimais, temos 1 byte. Por exemplo, 0xFF \u00e9 o byte FF (255). 0x01FF \u00e9 o byte 0x01 e 0xFF, e por a\u00ed vai.<\/li>\n<\/ul>\n<p>Conforme eu j\u00e1 informei, dependendo do seu backup, esta fun\u00e7\u00e3o pode trazer milhares de linhas. E como j\u00e1 deve ter percebido o SELECT nela n\u00e3o \u00e9 r\u00e1pido. Por isso, \u00e9 interessante separar somente os registros com os quais queria trabalhar. Neste script eu j\u00e1 fa\u00e7o tudo isso:<\/p>\n<p><strong><span style=\"color: #ff0000;\">Aqui fica um outro ALERTA: Executar isso em produ\u00e7\u00e3o sem saber exatamente o que est\u00e1 fazendo, pode causar extrema atividade de disco e CPU, impactando em suas queries! Tente usar uma outra inst\u00e2ncia, se poss\u00edvel (uma de teste, no Azure, etc.)! Execute por sua pr\u00f3pria conta e risco!<\/span><\/strong><\/p>\n<pre class=\"lang:tsql decode:true\" title=\"Recuperando os registros de DELETE\">USE TheSqlTimes\nGO\n\nDROP TABLE IF EXISTS dbo.LogDeletes\n\nSELECT\n     Lsn = [Current LSN]\n    ,Operation\n    ,Context\n    ,AllocUnitId\n    ,Registro = [RowLog Contents 0]\nINTO\n    LogDeletes\nFROM\n    fn_dump_dblog (\n        NULL, NULL, N'DISK', 1, N'T:\\Backup16h30.trn',\n        DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,\n        DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,\n        DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,\n        DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,\n        DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,\n        DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,\n        DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,\n        DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,\n        DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT) T\nWHERE\n    AllocUnitId IN (\n        SELECT\n            AU.allocation_unit_id\n        FROM\n            sys.partitions P\n            JOIN\n            sys.allocation_units AU ON AU.container_id = P.hobt_id\n        WHERE\n            P.object_id = OBJECT_ID('dbo.Cliente') AND P.index_id &lt;= 1\n    )\n    AND\n    Operation = 'LOP_DELETE_ROWS'\n    AND\n    Context  IN ('LCX_CLUSTERED','LCX_MARK_AS_GHOST')<\/pre>\n<p>Em nosso cen\u00e1rio, esta execu\u00e7\u00e3o me trouxe exatamente 10 mil linhas! Coincid\u00eancia (lembre-se que no \u00faltimo post que deletamos 10 mil linhas)? No Recovery FULL, o comando DELETE gera registros no log para cada linha que est\u00e1 sendo deletada (ao contr\u00e1rio do TRUNCATE TABLE, que loga cada p\u00e1gina). Com os filtros corretos, eu conseguir achar exatamente estas linhas:<\/p>\n<figure id=\"attachment_1369\" aria-describedby=\"caption-attachment-1369\" style=\"width: 1842px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/04\/img_5e8a4262885b6.png\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-1369 size-full\" src=\"http:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/04\/img_5e8a4262885b6.png\" alt=\"\" width=\"1842\" height=\"849\" srcset=\"https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/04\/img_5e8a4262885b6.png 1842w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/04\/img_5e8a4262885b6-300x138.png 300w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/04\/img_5e8a4262885b6-1024x472.png 1024w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/04\/img_5e8a4262885b6-768x354.png 768w, https:\/\/thesqltimes.com\/blog\/wp-content\/uploads\/2020\/04\/img_5e8a4262885b6-1536x708.png 1536w\" sizes=\"auto, (max-width: 1842px) 100vw, 1842px\" \/><\/a><figcaption id=\"caption-attachment-1369\" class=\"wp-caption-text\">Resultado do mesmo script acima<\/figcaption><\/figure>\n<p>Aqui \u00e9 importante lembrar que, em um cen\u00e1rio real, voc\u00ea pode ter al\u00e9m das linhas que deletou, logs de outras opera\u00e7\u00f5es, por exemplo, de outros DELETEs. Voc\u00ea vai precisar analisar cada caso para tomar as a\u00e7\u00f5es corretas. O importante aqui \u00e9: Sem o Backup FULL j\u00e1 cheguei nos meus registros e o n\u00famero de linhas me deu mais esperan\u00e7a!<\/p>\n<p>Ent\u00e3o, desde que deletamos os dados, j\u00e1 conseguimos, sem o Backup FULL, recuperar a mesma quantidade de linhas deletadas! Ser\u00e1 que estamos corretos at\u00e9 aqui? No pr\u00f3ximo post, vamos ver como transformamos isso de volta para o que conhecemos normalmente, que s\u00e3o as colunas desses registros!<\/p>\n<p><strong>Spoiler: Aproveite e revise o material do Paul Randal sobre a anatomia de um registro. Voc\u00ea vai precisar dessa base. Os links est\u00e3o no primeiro post da s\u00e9rie.<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"seriesmeta\">This entry is part 2 of 6 in the series <a href=\"https:\/\/thesqltimes.com\/blog\/series\/recuperando-dados-deletados-sem-backup-full\/\" class=\"series-294\" title=\"Recuperando dados deletados sem Backup Full\">Recuperando dados deletados sem Backup Full<\/a><\/div><p>Ol\u00e1! Este \u00e9 mais um post da s\u00e9rie que tem o intuito de mostrar uma, das v\u00e1rias maneiras, que voc\u00ea pode tentar para recuperar dados que voc\u00ea deletou do seu SQL Server e n\u00e3o tem o famoso backup FULL! No \u00faltimo e primeiro post da s\u00e9rie eu criei um cen\u00e1rio de exemplo e apresentei a&hellip;&nbsp;<a href=\"https:\/\/thesqltimes.com\/blog\/2020\/04\/12\/recuperar-delete-sem-backup-full-2\/\" rel=\"bookmark\"><span class=\"screen-reader-text\">Recuperando dados deletados do SQL Server sem Backup Full &#8211; Parte 2<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"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":[8,296,7],"tags":[174,305,301,306,304,307,73,308],"series":[294],"class_list":["post-1398","post","type-post","status-publish","format-standard","hentry","category-administracao","category-internals","category-sql-server","tag-backup","tag-delete","tag-fn_dump_dblog","tag-lcx_clustered","tag-lcx_mark_as_ghost","tag-log-operation","tag-sql-server","tag-transaciton-log","series-recuperando-dados-deletados-sem-backup-full"],"_links":{"self":[{"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/posts\/1398","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=1398"}],"version-history":[{"count":9,"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/posts\/1398\/revisions"}],"predecessor-version":[{"id":1483,"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/posts\/1398\/revisions\/1483"}],"wp:attachment":[{"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/media?parent=1398"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/categories?post=1398"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/tags?post=1398"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/thesqltimes.com\/blog\/wp-json\/wp\/v2\/series?post=1398"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}