Eu tenho uma tabela no SQL Server 2014 que se parece com o seguinte:
CREATE TABLE dbo.MyTable
(
[id1] [bigint] NOT NULL,
[id2] [bigint] NOT NULL,
[col1] [int] NOT NULL default(0),
[col2] [int] NOT NULL default(0)
)
com (id1, id2) sendo o PK. Basicamente, id1 é um identificador para agrupar um conjunto de resultados (id2, col1, col2), cujo pk é id2.
Estou tentando usar uma tabela na memória para se livrar de uma tabela existente baseada em disco, que é o meu gargalo.
- Os dados na tabela são gravados -> lidos -> excluídos uma vez.
- Cada valor de id1 possui vários (dezenas / centenas de) milhares de id2.
- Os dados são armazenados na tabela por um período muito curto, por exemplo, 20 segundos.
As consultas realizadas nesta tabela são as seguintes:
-- INSERT (can vary from 10s to 10,000s of records):
INSERT INTO MyTable
SELECT @fixedValue, id2, col1, col2 FROM AnotherTable
-- READ:
SELECT id2, col1
FROM MyTable INNER JOIN OtherTbl ON MyTable.id2 = OtherTbl.pk
WHERE id1 = @value
ORDER BY col1
-- DELETE:
DELETE FROM MyTable WHERE id1 = @value
Aqui está a definição atual que eu usei para a tabela:
CREATE TABLE dbo.SearchItems
(
[id1] [bigint] NOT NULL,
[id2] [bigint] NOT NULL,
[col1] [int] NOT NULL default(0),
[col2] [int] NOT NULL default(0)
CONSTRAINT PK_Mem PRIMARY KEY NONCLUSTERED (id1,id2),
INDEX idx_Mem HASH (id1,id2) WITH (BUCKET_COUNT = 131072)
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY)
Infelizmente, essa definição resulta em uma degradação do desempenho em relação à situação anterior com uma tabela baseada em disco. A ordem de grandeza é mais ou menos 10% maior (que em alguns casos atinge 100%, então o dobro do tempo).
Acima de tudo, eu esperava obter uma super vantagem em cenários de alta simultaneidade, dada a arquitetura sem bloqueios anunciada pela Microsoft. Em vez disso, os piores desempenhos são exatamente quando há vários usuários simultâneos executando várias consultas na tabela.
Questões:
- qual é o BUCKET_COUNT correto para definir?
- que tipo de índice devo usar?
- por que o desempenho é pior do que com a tabela baseada em disco?
Uma consulta de sys.dm_db_xtp_hash_index_stats retorna:
total_bucket_count = 131072 empty_bucket_count = 0 avg_chain_len = 873 max_chain_length = 1009
Alterei a contagem de buckets para que a saída de sys.dm_db_xtp_hash_index_stats seja:
total_bucket_count = 134217728 empty_bucket_count = 131664087 avg_chain_len = 1 max_chain_length = 3
Ainda assim, os resultados são quase os mesmos, se não piores.
fonte
OPTION(OPTIMIZE FOR UNKNOWN)
(consulte Dicas de tabela )?select * from sys.dm_db_xtp_hash_index_stats
? Além disso, esta ligação deve responder a maioria / todas as suas perguntas: msdn.microsoft.com/en-us/library/...Respostas:
Embora esta postagem não seja uma resposta completa devido à falta de informações, ela poderá direcioná-lo na direção correta ou obter informações que você poderá compartilhar posteriormente com a comunidade.
Isso é preocupante, pois definitivamente não deve ser o caso. Certas cargas de trabalho não são encontradas em tabelas de memória (SQL 2014) e algumas cargas de trabalho se prestam a ela. Na maioria das situações, pode haver um aumento mínimo no desempenho apenas migrando e escolhendo os índices adequados.
Originalmente, eu estava pensando muito estreitamente sobre suas perguntas sobre isso:
Inicialmente, eu acreditava que havia um problema com o real na tabela de memória e os índices não sendo ideais. Embora existam alguns problemas com a definição de índice de hash otimizada para memória, acredito que o problema real esteja nas consultas usadas.
Essa inserção deve ser extremamente rápida se envolver apenas a tabela na memória. No entanto, também envolve uma tabela baseada em disco e está sujeita a todos os bloqueios e bloqueios associados a ela. Portanto, o desperdício em tempo real aqui está na tabela baseada em disco.
Quando fiz um teste rápido contra 100.000 linhas de inserção da tabela com base em disco após carregar os dados na memória - foram tempos de resposta em segundos. No entanto, a maioria dos seus dados é mantida apenas por um período muito curto, inferior a 20 segundos. Isso não dá muito tempo para realmente viver em cache. Além disso, não tenho certeza de quão grande
AnotherTable
é realmente e não sei se os valores estão sendo lidos fora do disco ou não. Temos que confiar em você para obter essas respostas.Com a consulta Selecionar:
Novamente, estamos à mercê do desempenho da tabela baseada em interoperabilidade + disco. Além disso, as classificações não são baratas nos índices HASH e um índice não clusterizado deve ser usado. Isso é mencionado no guia Índice que eu vinculei nos comentários.
Para fornecer alguns fatos reais baseados em pesquisas, carreguei a
SearchItems
tabela na memória com 10 milhões de linhas eAnotherTable
com 100.000, pois não conhecia o tamanho real ou as estatísticas dela. Eu então usei a consulta de seleção acima para executar. Além disso, criei uma sessão de eventos estendida em wait_completed e a coloquei em um buffer de anel. Foi limpo após cada execução. Também corriDBCC DROPCLEANBUFFERS
para simular um ambiente em que todos os dados podem não residir na memória.Os resultados não foram nada espetaculares quando os observamos no vácuo. Como o laptop em que estou testando está usando um SSD de nível superior, diminuí artificialmente o desempenho baseado em disco da VM que estou usando.
Os resultados chegaram sem informações de espera após 5 execuções da consulta apenas na tabela baseada na memória (removendo a junção e sem subconsultas). Isso é praticamente o esperado.
Ao usar a consulta original, no entanto, tive esperas. Nesse caso, foi PAGEIOLATCH_SH que faz sentido à medida que os dados estão sendo lidos no disco. Como sou o único usuário deste sistema e não gastei tempo para criar um ambiente de teste massivo para inserções, atualizações e exclusões na tabela unida, não esperava que nenhum bloqueio ou bloqueio entre em vigor.
Nesse caso, mais uma vez, a parte significativa do tempo foi gasta na tabela baseada em disco.
Finalmente, a consulta de exclusão. Encontrar as linhas baseadas apenas no ID1 não é extremamente eficiente com um índice has. Embora seja verdade que os predicados de igualdade são os índices de hash adequados, o intervalo no qual os dados se encaixam é baseado em todas as colunas de hash. Assim, id1, id2, em que id1 = 1, id2 = 2 e id1 = 1, id2 = 3 serão hash em diferentes buckets, pois o hash estará entre (1,2) e (1,3). Não será uma varredura simples do intervalo B-Tree, pois os índices de hash não estão estruturados da mesma maneira. Eu esperaria que este não fosse o índice ideal para esta operação, no entanto, não esperaria que ele levasse ordens de magnitude mais longas do que as experimentadas. Eu estaria interessado em ver o wait_info sobre isso.
Embora seja verdade que os bloqueios são usados para consistência lógica, as operações ainda devem ser atômicas. Isso é feito por meio de um operador de comparação especial baseado em CPU (e é por isso que o In-Memory funciona apenas com determinados processadores [embora quase todos os cpus fabricados nos últimos 4 anos]). Portanto, não temos tudo de graça, ainda haverá tempo para concluir essas operações.
Outro ponto a ser destacado é o fato de que em quase todas as consultas, a interface usada é o T-SQL (e não os SPROCs compilados nativamente), que tocam em pelo menos uma tabela baseada em disco. É por isso que acredito que, no final, não estamos tendo nenhum desempenho aprimorado, pois ainda estamos limitados ao desempenho das tabelas baseadas em disco.
Acompanhamento:
Crie uma sessão de evento estendida para wait_completed e especifique um SPID conhecido por você. Execute a consulta e forneça a saída ou a consuma internamente.
Dê-nos uma atualização sobre a saída do # 1.
Não há um número mágico para determinar a contagem de buckets para índices de hash. Basicamente, desde que os baldes não fiquem completamente cheios e as cadeias de linhas fiquem abaixo de 3 ou 4, o desempenho deve permanecer aceitável. É como perguntar: "O que devo definir no meu arquivo de log?" - vai depender por processo, por banco de dados, por tipo de uso.
fonte