Queries que não diferenciam maiúsculas de minúsculas sem índices que não diferenciam maiúsculas de minúsculas
Lauren Schaefer, Daniel Coupal8 min read • Published Feb 12, 2022 • Updated Oct 01, 2024
Avalie esse Artigo
Começamos o sexto e último (pelo menos por enquanto) antipadrão de projeto de esquema do MongoDB. Nas primeiras cinco publicações desta série, abordamos os seguintes antipadrões.
Hoje, exploraremos o maravilhoso mundo dos índices que não diferenciam maiúsculas de minúsculas. Não ter um índice que não diferencia maiúsculas de minúsculas pode gerar resultados surpreendentes de queries e/ou queries lentas... e fazer com que você odeie tudo.
Depois de saber melhor como as queries que diferenciam maiúsculas de minúsculas funcionam, a implementação é bastante simples. Vamos nessa!
Assista ao vídeo acima para ver as queries e os índices que não diferenciam maiúsculas de minúsculas em ação.
O MongoDB permite três maneiras principais de executar queries que não diferenciam maiúsculas de minúsculas.
Primeiro, você pode executar uma query que não diferencia maiúsculas de minúsculas usando $regex com a opção
i
. Essas queries fornecerão os resultados esperados que não diferenciam maiúsculas de minúsculas. No entanto, as queries que usam $regex
não lidam bem com índices que não diferenciam maiúsculas de minúsculas, então essas queries podem ser muito lentas dependendo da quantidade de dados na sua coleção.Em segundo lugar, você pode executar uma query que não diferencia maiúsculas de minúsculas criando um índice (o que significa que ele tem uma intensidade de agrupamento de
1
ou 2
) e executando uma query com o mesmo agrupamento do índice. Um agrupamento define as regras específicas da linguagem que o MongoDB usará para comparação de string. Opcionalmente, os índices podem ter um agrupamento com uma intensidade que varia de 1 a 5. As intensidades de 1
e de agrupamento 2
garantem diferenciação entre maiúsculas e minúsculas. Para obter mais informações sobre as diferenças das intensidades de agrupamento, consulte a documentação do MongoDB . Uma query executada com o mesmo agrupamento de um índice que não diferencia maiúsculas de minúsculas retornará resultados que não diferenciam maiúsculas de minúsculas. Como essas queries são abrangidas por índices, elas são executadas muito rapidamente.A terceira opção é executar uma query que diferencia maiúsculas de minúsculas definindo a intensidade de agrupamento padrão para queries e índices como uma intensidade de
1
ou 2
ao criar uma coleção. Todas as queries e índices em uma coleção usam automaticamente o agrupamento padrão, a menos que você especifique algo diferente disso ao executar uma query ou criar um índice. Portanto, ao definir o agrupamento padrão com uma intensidade de 1
ou 2
, você obterá queries e índices que diferenciam maiúsculas de minúsculas por padrão. Consulte a opção collation
na seção db.createCollection() da documentação do MongoDB para saber mais informações.Aviso para queries que não usam
$regex
: seu índice deve ter uma intensidade de agrupamento de 1
ou 2
e sua query deve usar o mesmo agrupamento que o índice para que a query não diferencie maiúsculas de minúsculas.Você pode usar o MongoDB Compass (interface de usuário da versão desktop do MongoDB) ou o MongoDB Shell (a ferramenta de linha de comando do MongoDB) para testar se uma query está retornando os resultados esperados, ver seu tempo de execução e determinar se está usando um índice.
Vamos voltar para o exemplo que vimos nas publicações Antipadrões de índices desnecessários e Antipadrões de documentos volumosos. Leslie está criando um site que apresenta com mulheres inspiradoras. Ela criou um banco de dados com informações sobre mais de 4.700mulheres inspiradoras. Abaixo estão três documentos em sua coleção
InspirationalWomen
.1 { 2 "_id": ObjectId("5ef20c5c7ff4160ed48d8f83"), 3 "first_name": "Harriet", 4 "last_name": "Tubman", 5 "quote": "I was the conductor of the Underground Railroad for eight years, 6 and I can say what most conductors can't say; I never ran my 7 train off the track and I never lost a passenger" 8 }, 9 { 10 "_id": ObjectId("5ef20c797ff4160ed48d90ea"), 11 "first_name": "HARRIET", 12 "middle_name": "BEECHER", 13 "last_name": "STOWE", 14 "quote": "When you get into a tight place and everything goes against you, 15 till it seems as though you could not hang on a minute longer, 16 never give up then, for that is just the place and time that 17 the tide will turn." 18 }, 19 { 20 "_id": ObjectId("5ef20c937ff4160ed48d9201"), 21 "first_name": "Bella", 22 "last_name": "Abzug", 23 "quote": "This woman's place is in the House—the House of Representatives." 24 }
A Leslie decide adicionar um recurso de pesquisa ao seu site, porque está difícil navegar nele. Ela começa a implementar o recurso de pesquisa criando um índice no campo
first_name
. Em seguida, ela começa a testar uma query que buscará mulheres chamadas "Harriet".A Leslie executa a seguinte query no MongoDB Shell:
1 db.InspirationalWomen.find({first_name: "Harriet"})
Ela fica surpresa que a query retorna apenas um documento, porque tem duas Harriets em seu banco de dados: Harriet Tubman e Harriet Beecher Stowe. Então ela percebe que o nome de Harriet Beecher Stowe foi inserido todo em letras maiúsculas no banco de dados. Sua query diferencia maiúsculas de minúsculas, porque não está usando um índice sem distinção de maiúsculas e minúsculas.
1 db.InspirationalWomen.find({first_name: "Harriet"}).explain("executionStats")
O Shell retorna a seguinte saída.
1 { 2 "queryPlanner": { 3 ... 4 "winningPlan": { 5 "stage": "FETCH", 6 "inputStage": { 7 "stage": "IXSCAN", 8 "keyPattern": { 9 "first_name": 1 10 }, 11 "indexName": "first_name_1", 12 ... 13 "indexBounds": { 14 "first_name": [ 15 "[\"Harriet\", \"Harriet\"]" 16 ] 17 } 18 } 19 }, 20 "rejectedPlans": [] 21 }, 22 "executionStats": { 23 "executionSuccess": true, 24 "nReturned": 1, 25 "executionTimeMillis": 0, 26 "totalKeysExamined": 1, 27 "totalDocsExamined": 1, 28 "executionStages": { 29 ... 30 } 31 } 32 }, 33 ... 34 }
Ela pode ver que
winningPlan
está usando um IXSCAN
(verificação de índice) com seu índice first_name_1
. Em executionStats
, ela vê que apenas uma chave de índice foi analisada (executionStats.totalKeysExamined
) e apenas um documento foi analisado (executionStats.totalDocsExamined
). Para obter mais informações sobre como interpretar a saída de .explain()
, consulte Analisar o desempenho da query.Leslie abre o Compass e vê resultados semelhantes.
first_name_1
.Leslie quer que todas as Harriets (independentemente de estar escrito com letra maiúscula ou minúscula) sejam retornadas em sua query. Ela atualiza sua query para usar
$regex
com a opção i
para indicar que a expressão regular deve diferenciar maiúsculas de minúsculas. Ela retorna ao Shell e executa sua nova query:1 db.InspirationalWomen.find({first_name: { $regex: /Harriet/i} })
Desta vez, ela obtém os resultados que esperava: os documentos de Harriet Tubman e também de Harriet Beecher Stowe. Leslie fica muito feliz! Ela executa a query novamente com
.explain("executionStats")
para obter detalhes sobre a execução da query. Veja abaixo o que o Shell retorna:1 { 2 "queryPlanner": { 3 ... 4 "winningPlan": { 5 "stage": "FETCH", 6 "inputStage": { 7 "stage": "IXSCAN", 8 "filter": { 9 "first_name": { 10 "$regex": "Harriet", 11 "$options": "i" 12 } 13 }, 14 "keyPattern": { 15 "first_name": 1 16 }, 17 "indexName": "first_name_1", 18 ... 19 "indexBounds": { 20 "first_name": [ 21 "[\"\", {})", 22 "[/Harriet/i, /Harriet/i]" 23 ] 24 } 25 } 26 }, 27 "rejectedPlans": [] 28 }, 29 "executionStats": { 30 "executionSuccess": true, 31 "nReturned": 2, 32 "executionTimeMillis": 3, 33 "totalKeysExamined": 4704, 34 "totalDocsExamined": 2, 35 "executionStages": { 36 ... 37 } 38 }, 39 ... 40 }
Ela pode ver que esta query, como a anterior, usa um índice (
IXSCAN
). No entanto, como as queries $regex
não podem utilizar eficientemente índices que não diferenciam maiúsculas de minúsculas, ela não está obtendo os benefícios típicos de uma query coberta por um índice. Todas as 4.704 chaves de índice (executionStats.totalKeysExamined
) estão sendo examinadas como parte desta query, resultando em uma query um pouco mais lenta (executionStats.executionTimeMillis: 3
) do que aquela que utiliza totalmente um índice.Ela executa a mesma query no Compass e vê resultados semelhantes. A query está usando seu índice
first_name_1
, mas examinando cada chave de índice.$regex
está usando o índice first_name_1
, mas examinando cada chave de índice.Leslie quer garantir que seu recurso de pesquisa seja executado o mais rápido possível. Ela usa o Compass para criar um novo índice que não diferencia maiúsculas de minúsculas chamado
first_name-case_insensitive
. E ela pode criar índices com facilidade usando outras ferramentas, como o Shell ou o MongoDB Atlas ou até mesmo pelo código de programação. O índice dela estará no campo first_name
em ordem crescente e usará um agrupamento personalizado com um locale en
e uma intensidade de 2
. Lembre-se, como vimos na seção anterior, que a intensidade do agrupamento deve ser definida como 1
ou 2
para que o índice não diferencie maiúsculas de minúsculas.en
e uma intensidade de 2
.Leslie executa uma query muito semelhante à sua query original no Shell, mas desta vez ela especifica o agrupamento que corresponde ao índice recém-criado:
1 db.InspirationalWomen.find({first_name: "Harriet"}).collation( { locale: 'en', strength: 2 } )
Desta vez, os resultados mostram tanto Harriet Tubman quanto Harriet Beecher Stowe. Sucesso!
Ela executa a query com
.explain("executionStats")
para garantir que a query esteja usando seu índice:1 db.InspirationalWomen.find({first_name: "Harriet"}).collation( { locale: 'en', strength: 2 } ).explain("executionStats")
O Shell retorna os seguintes resultados.
1 { 2 "queryPlanner": { 3 ... 4 "collation": { 5 "locale": "en", 6 ... 7 "strength": 2, 8 ... 9 }, 10 "winningPlan": { 11 "stage": "FETCH", 12 "inputStage": { 13 "stage": "IXSCAN", 14 "keyPattern": { 15 "first_name": 1 16 }, 17 "indexName": "first_name-case_insensitive", 18 "collation": { 19 "locale": "en", 20 ... 21 "strength": 2, 22 ... 23 }, 24 ... 25 "indexBounds": { 26 "first_name": [ 27 "[\"7)KK91O\u0001\u000b\", \"7)KK91O\u0001\u000b\"]" 28 ] 29 } 30 } 31 }, 32 "rejectedPlans": [] 33 }, 34 "executionStats": { 35 "executionSuccess": true, 36 "nReturned": 2, 37 "executionTimeMillis": 0, 38 "totalKeysExamined": 2, 39 "totalDocsExamined": 2, 40 "executionStages": { 41 ... 42 } 43 } 44 }, 45 ... 46 }
Leslie nota que a opção de sucesso é executar um
IXSCAN
(verificação de índice) que usa o índice que não diferencia maiúsculas de minúsculas que ela acabou de criar. Duas chaves de índice (executionStats.totalKeysExamined
) estão sendo examinadas e dois documentos (executionStats.totalDocsExamined
) estão sendo examinados. A query está sendo executada em 0 ms (executionStats.executionTimeMillis: 0
). Isso é muito rápido!Leslie executa a mesma query no Compass e especifica o agrupamento que a query deve usar.
Ela pode ver que a query está usando seu índice que não diferencia maiúsculas de minúsculas e está sendo executada em 0 ms. Ela está pronta para implementar seu recurso de pesquisa. É hora de comemorar!
Observação: outra opção para Leslie teria sido definir a força de agrupamento padrão de sua coleção "mulheres inspiradoras" como
1
ou 2
quando ela criou a coleção. Em seguida, todas as suas queries teriam retornado os resultados esperados, sem distinção de maiúsculas e minúsculas, independentemente de ela ter criado um índice ou não. Ela ainda gostaria de criar índices para aumentar o desempenho de suas queries.Você tem três opções principais quando deseja executar uma query sem diferenciar maiúsculas de minúsculas:
- Use
$regex
com a opçãoi
. Mas saiba que essa opção não tem um desempenho tão bom, porque$regex
não pode utilizar totalmente os índices que não diferenciam maiúsculas de minúsculas. - Crie um índice que não diferencie maiúsculas de minúsculas com uma força de agrupamento de
1
ou2
e especifique que sua query use o mesmo agrupamento. - Defina a força de agrupamento padrão da sua coleção como
1
ou2
ao criá-la e não especifique um agrupamento diferente em suas queries e índices.
Esta publicação é último antipadrão que abordaremos nesta série. Mas não precisa ficar triste, esta não é a última publicação da série. Aguarde a próxima publicação, em que resumiremos todos os antipadrões e mostraremos um novo recurso do MongoDB Atlas que ajudará a descobrir antipadrões em seu banco de dados. Não perca!
Quando estiver pronto para criar um esquema no MongoDB, confira o MongoDB Atlas, o banco de dados como serviço totalmente gerenciado do MongoDB. O Atlas é a maneira mais fácil de começar a usar o MongoDB e tem uma camada rica e gratuita para sempre.
Confira os seguintes recursos para obter mais informações: