Se você nunca planeje procurar atributos específicos, é uma má ideia serializá-los em uma única coluna, pois você terá que usar funções por linha para obter as informações - isso raramente escala bem.
Eu optaria pela sua segunda opção. Tenha uma lista de atributos em uma tabela de atributos, os objetos em sua própria tabela e uma tabela de relacionamento muitos-para-muitos chamada atributos de objeto.
Por exemplo:
objects:
object_id integer
object_name varchar(20)
primary key (object_id)
attributes:
attr_id integer
attr_name varchar(20)
primary key (attr_id)
object_attributes:
object_id integer references (objects.object_id)
attr_id integer references (attributes.attr_id)
oa_value varchar(20)
primary key (object_id,attr_id)
Sua preocupação com o desempenho é notada, mas, na minha experiência, é sempre mais caro dividir uma coluna do que combinar várias colunas. Se houver problemas de desempenho, é perfeitamente aceitável quebrar a 3NF por motivos de desempenho.
Nesse caso, eu o armazenaria da mesma maneira, mas também teria uma coluna com os dados serializados brutos. Desde que você use acionadores de inserção/atualização para manter os dados colunares e combinados sincronizados, você não terá problemas. Mas você não deve se preocupar com isso até que um problema real surja.
Ao usar esses gatilhos, você minimiza o trabalho necessário para somente quando os dados mudam. Ao tentar extrair informações de subcolunas, você faz um trabalho desnecessário em cada selecione.