Here's a not-all-that-optimal solution. It gets round the main problem but introduces a few suboptimal pieces. (Hopefully someone who understood the externalisation process better than I do would be able to fix those ... looking at no Feuersängers in particular.)
The main problem is when the command \only<2>{...} refers to a frame that is not referenced in the rest of the picture. Then beamer has to know that a new frame is required. It does this by setting a flag when processing a version of the frame that says "I've encountered an overlay specification that refers to a slide in the future so keep going.". However, when this is done inside the picture then this information is only known to the tex process doing the picture. It is not communicated back to the main process. All the main process knows is that there is a picture there so it doesn't know to rerun the frame. If it did rerun the frame it would generate the next version of the picture: that is what Christian's trick is doing (incidentally, it doesn't need the \only<1-2>{...} around the picture; anywhere on the frame will do; even \begin{frame}<1-2> will do). So we need to get that information back. Fortunately, the external library does produce a communication channel: the .dpth file. Unfortunately, due to some internal optimisation stuff, when processing a particular picture the relevant .dpth files for other pictures don't get read. So, in the above example, we can make the first run flag that another run is needed. The main tex process gets that and does another run but when the second subprocess runs it doesn't know that two runs are needed and so complains vociferously.
It is possible to turn off this optimisation which ensures that the .dpth file is read. A better solution would be to have only partial optimisation where the .dpth file was read but the rest thrown away.
The other drawback of this solution is that a new picture is generated even if it hasn't actually changed. So if the same picture appears on slides 1 and 2 but changes on 3 then 3 versions are generated. Again, some wizard could probably fix this with md5s.
Lastly, there's some weird hspacing issues creeping in: run it two or three times to see them. Fortunately not cumulative but presumably some space didn't get %'ed out.
\documentclass{beamer}
% main document, called main.tex
\usepackage{tikz}
\usetikzlibrary{external}
\tikzexternalize % activate!
\makeatletter
\tikzset{
beamer externalising/.style={%
execute at end picture={%
\tikzifexternalizing{%
\ifbeamer@anotherslide
\pgfexternalstorecommand{\string\global\string\beamer@anotherslidetrue}%
\fi
}{}%
}%
},
external/optimize=false
}
\makeatother
\begin{document}
\begin{frame}
\noindent % create a new paragraph here - otherwise, we might end up
with different hspace.
\begin{tikzpicture}[beamer externalising]
\node at (1,1) {test};
\only<2>{\node at (2,2) {test2};}
\end{tikzpicture}%
\end{frame}
\end{document}