Em primeiro lugar, a raison d'être (razão de ser) de um banco de dados relacional é ser capaz de modelar relacionamentos entre entidades. As junções são simplesmente os mecanismos pelos quais atravessamos esses relacionamentos. Eles certamente têm um custo nominal, mas sem junções, realmente não há razão para ter um banco de dados relacional.
No mundo acadêmico, aprendemos coisas como as várias formas normais (1ª, 2ª, 3ª, Boyce-Codd, etc.), e aprendemos sobre diferentes tipos de chaves (primária, estrangeira, alternativa, única, etc.) e como essas coisas se encaixam para criar um banco de dados. E aprendemos os rudimentos de SQL e também manipulamos a estrutura e os dados (DDL e DML).
No mundo corporativo, muitos dos conceitos acadêmicos se revelaram substancialmente menos viáveis do que fomos levados a acreditar. Um exemplo perfeito é a noção de chave primária. Academicamente, é esse atributo (ou coleção de atributos) que identifica exclusivamente uma linha na tabela. Portanto, em muitos domínios de problemas, a chave primária acadêmica adequada é um composto de 3 ou 4 atributos. No entanto, quase todo mundo no mundo corporativo moderno usa um inteiro sequencial gerado automaticamente como a chave primária de uma tabela. Por quê? Duas razões. A primeira é porque torna o modelo muito mais limpo quando você está migrando FKs para todos os lugares. A segunda, e mais pertinente a essa questão, é que recuperar dados por meio de junções é mais rápido e mais eficiente em um único inteiro do que em 4 colunas varchar (como já foi mencionado por algumas pessoas).
Vamos cavar um pouco mais fundo agora em dois subtipos específicos de bancos de dados do mundo real. O primeiro tipo é um banco de dados transacional. Essa é a base para muitos aplicativos de e-commerce ou gerenciamento de conteúdo que conduzem sites modernos. Com um banco de dados de transação, você está otimizando fortemente em direção ao "rendimento da transação". A maioria dos aplicativos comerciais ou de conteúdo precisa equilibrar o desempenho de consulta (de certas tabelas) com o desempenho de inserção (em outras tabelas), embora cada aplicativo tenha seus próprios problemas específicos de negócios para resolver.
O segundo tipo de banco de dados do mundo real é um banco de dados de relatórios. Eles são usados quase exclusivamente para agregar dados de negócios e gerar relatórios de negócios significativos. Eles são tipicamente moldados de forma diferente dos bancos de dados de transações onde os dados são gerados e são altamente otimizados para velocidade de carregamento de dados em massa (ETLs) e desempenho de consulta com conjuntos de dados grandes ou complexos.
Em cada caso, o desenvolvedor ou DBA precisa equilibrar cuidadosamente as curvas de funcionalidade e desempenho, e há muitos truques para melhorar o desempenho em ambos os lados da equação. No Oracle, você pode fazer o que é chamado de "plano de explicação" para ver especificamente como uma consulta é analisada e executada. Você está procurando maximizar o uso adequado de índices pelo banco de dados. Um não-não realmente desagradável é colocar uma função na cláusula where de uma consulta. Sempre que você fizer isso, você garante que o Oracle não usará nenhum índice nessa coluna específica e provavelmente verá uma varredura de tabela completa ou parcial no plano de explicação. Esse é apenas um exemplo específico de como uma consulta pode ser escrita que acaba sendo lenta e não tem nada a ver com junções.
E enquanto estamos falando sobre varreduras de tabela, elas obviamente afetam a velocidade da consulta proporcionalmente ao tamanho da tabela. Uma varredura completa da tabela de 100 linhas nem é perceptível. Execute a mesma consulta em uma tabela com 100 milhões de linhas e você precisará voltar na próxima semana para o retorno.
Vamos falar sobre normalização por um minuto. Este é outro tópico acadêmico amplamente positivo que pode ser excessivamente estressado. Na maioria das vezes, quando falamos sobre normalização, realmente queremos dizer a eliminação de dados duplicados, colocando-os em sua própria tabela e migrando um FK. As pessoas geralmente ignoram toda a coisa de dependência descrita por 2NF e 3NF. E ainda assim, em um caso extremo, certamente é possível ter um banco de dados BCNF perfeito que é enorme e uma besta completa para escrever código porque é muito normalizado.
Então, onde nos equilibramos? Não existe uma única resposta melhor. Todas as melhores respostas tendem a ser um meio-termo entre facilidade de manutenção da estrutura, facilidade de manutenção de dados e facilidade de criação / manutenção de código. Em geral, quanto menos duplicação de dados, melhor.
Então, por que as junções às vezes são lentas? Às vezes, é um design relacional ruim. Às vezes, é uma indexação ineficaz. Às vezes, é um problema de volume de dados. Às vezes, é uma consulta escrita de maneira horrível.
Desculpe por uma resposta tão prolixa, mas me senti compelido a fornecer um contexto mais substancial em torno de meus comentários, em vez de apenas recitar uma resposta de quatro pontos.