query_plan_hash are used to look up query plans for execution, so the exact concern in the question does not arise.
The documentation for
sys.dm_exec_query_stats explains that:
query_hash is provided to allow users to aggregate resource usage for queries that differ only by literal values (ones that would parameterize to the same form).
query_plan_hash is provided to allow users to calculate the cumulative cost of queries with similar execution plans.
Unsurprisingly, the SQL Server developers have gone to great lengths to ensure that cached plans are only reused when it is safe to do so. This goes well beyond the obvious requirement that the cached plan must match the 'query' exactly.
SQL Server has multiple plan cache stores, each implemented as a hash table. A hash value and cache key pair is used to connect a compiled plan with a particular 'query'.
The hash value is
(database_id * object_id) mod (hash table size). This determines the hash bucket in a particular cache store. The
object_id is known metadata for objects like procedures, functions, and triggers. For ad-hoc SQL, the
object_id is a hash of the entire batch text.
The collection of attributes that must match for a cached plan to match a 'query' is known as the cache key. This is used to find and confirm an exact match within the hash bucket. Some (not all) cache key attributes can be found in the documentation for sys.dm_exec_plan_attributes.
Importantly, for ad-hoc SQL, the entire batch text is one of the cache key attributes.
More details can be found in 3.0 How Cache Lookups Work (and other articles in the series) by the Microsoft SQL Programmability & API Development Team.