Esse comportamento está documentado (parágrafo entre parênteses):
Se você especificar ON DUPLICATE KEY UPDATE e for inserida uma linha que causará um valor duplicado em um índice UNIQUE ou PRIMARY KEY, o MySQL executará um UPDATE da linha antiga. Por exemplo, se a coluna a for declarada como UNIQUE e contiver o valor 1, as duas instruções a seguir terão efeito semelhante:
INSERT INTO table (a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1; UPDATE table SET c=c+1 WHERE a=1;
(Os efeitos não são idênticos para uma tabela InnoDB onde a é uma coluna de incremento automático. Com uma coluna de incremento automático, uma instrução INSERT aumenta o valor de incremento automático, mas UPDATE não.)
Aqui está uma explicação simples. O MySQL tenta fazer a inserção primeiro. É quando o id é incrementado automaticamente. Uma vez incrementado, ele permanece. Em seguida, a duplicata é detectada e a atualização acontece. Mas o valor é perdido.
Você não deve depender de
auto_increment
não ter folgas. Se isso for um requisito, a sobrecarga nas atualizações e inserções será muito maior. Essencialmente, você precisa colocar um bloqueio em toda a tabela e renumerar tudo o que precisa ser renumerado, normalmente usando um gatilho. Uma solução melhor é calcular valores incrementais na saída.