Side effects are no issue if every local variable name in a defmacro is generated with (gensym).
See e.g. Alexandria[0] for with-gensyms (automatically binding your variables to gensyms) and once-only (similar, but ensures the value will be evaluated only once).
Also note that lack of hygiene is actually a useful feature - it allows macros to capture variables from code passed to it as well as the environment they expand it. Some cool stuff can be done that way (some uncool foot-shooting too, though).
--
[0] - https://common-lisp.net/project/alexandria/draft/alexandria....
Doug Hoyte's excellent book _Let Over Lambda_ has a lot of examples.