allow-query governs who can send any query to the server, not just queries against authoritative data. If a query is blocked by this ACL, the response sent back is empty (no records), with the RCODE set to REFUSED.
allow-query-cache was added in BIND 9.4 (previously, the only access control on cached data was allow-query). It is used to restrict who has access to records that are in cache (i.e. that have been learned by the recursive server via recursion).
If a query is blocked by allow-query-cache, the response is REFUSED, as with allow-query. If it passes allow-query-cache but is blocked by allow-recursion (an unusual situation these days), the query is handled as if it were not recursive.
allow-recursion and allow-query-cache default to behave like each other. In other words, if one is set but not the other, the behavior is as if both were set to the same ACL. Therefore, there's almost never any reason to set allow-query-cache - just use allow-recursion for both, unless you truly have a use for allowing someone access to the cache who does not also have permission to send recursive queries to the server.
The defaults if no values are set at all:
allow-query { any; };
allow-query-cache { localhost; localnets; };
allow-recursion { localhost; localnets; };
(With thanks to Chris Buxton, then of Men and Mice, for the original explanation posted on the bind-users mailing list from which this is adapted).