As Mark pointed out, you're going to be hard-pressed to find performance differences; I think other factors will be more important. For me, I always use COALESCE, and most of this has already been mentioned by you or Mark:
- COALESCE is ANSI standard. It's one less thing I have to worry about if I'm going to port my code. For me personally this is not that important, because I know how infrequently such ports actually happen outside of Celko's classroom world, but to some people this is a benefit.
- Contrary to what you said about readability, I find it can be harder to read ISNULL especially for users coming from other languages or platforms where ISNULL returns a boolean (which doesn't exist in SQL Server). Granted, COALESCE is harder to spell, but at least it doesn't lead to incorrect assumptions.
- COALESCE is a lot more flexible, as I can say COALESCE(a,b,c,d) whereas with ISNULL I'd have to do a lot of nesting to achieve the same thing.
You should also be sure you are aware of how data type precedence is handled using the two functions if you are using it with different data types/precisions etc.
There is one exception. These are handled differently in current versions of SQL Server:
SELECT COALESCE((SELECT some_aggregate_query),0);
SELECT ISNULL((SELECT some_aggregate_query),0);
COALESCE variant will actually execute
some_aggregate_query twice (once to check the value, and once to return it when non-zero), while
ISNULL will only execute the subquery once. I talk about a few other differences here: