diff --git a/mysql-test/main/subquery_cache_varchar_ref_key.result b/mysql-test/main/subquery_cache_varchar_ref_key.result new file mode 100644 index 0000000000000..8586861f71e9d --- /dev/null +++ b/mysql-test/main/subquery_cache_varchar_ref_key.result @@ -0,0 +1,118 @@ +# +# A ref lookup key built over a VARCHAR is a fixed width image, so the +# bytes that follow the value take part in the key comparison that +# decides whether the previous lookup can be reused, although they +# carry no information. A storage engine is free to leave that part of +# a record undefined, and the subquery cache reaches that comparison +# once per row. Only a build with memory instrumentation can fail on +# the read, so each block below also checks that the subquery cache +# counters moved, which is what shows the comparison was reached at +# all. Only whether they moved is checked, because a prepared +# statement executes the subquery twice and doubles them. +# +# The column has to be wider than 255 bytes. A key field always carries +# a two byte length prefix, and the full width copy that leaves the +# bytes after the value in place is taken only when the source field +# agrees on the width of that prefix. A narrower column takes a +# conversion path that writes the value alone. +# +CREATE TABLE t1 (id INT PRIMARY KEY, a VARCHAR(256)) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1,'foo'),(2,'bar'),(3,'foo'),(4,'quux'), +(5,'bar'),(6,'foo'),(7,'corge'),(8,'bar'); +CREATE TABLE t2 (x INT) ENGINE=InnoDB; +INSERT INTO t2 VALUES (1),(2); +# Values of differing length, so that the bytes after a short one are +# whatever the longer row before it left behind +# The predicate is false for every row, so nothing is updated +FLUSH STATUS; +UPDATE t1, t2 SET id = id + 100 WHERE a IN ( SELECT 'baz' UNION SELECT 'qux' ); +SELECT VARIABLE_NAME, VARIABLE_VALUE > 0 AS reached +FROM information_schema.SESSION_STATUS +WHERE VARIABLE_NAME LIKE 'SUBQUERY_CACHE%' ORDER BY VARIABLE_NAME; +VARIABLE_NAME reached +SUBQUERY_CACHE_HIT 1 +SUBQUERY_CACHE_MISS 1 +SELECT id, a FROM t1 ORDER BY id; +id a +1 foo +2 bar +3 foo +4 quux +5 bar +6 foo +7 corge +8 bar +# The same lookups with three rows qualifying +FLUSH STATUS; +UPDATE t1, t2 SET id = id + 10 WHERE a IN ( SELECT 'foo' UNION SELECT 'qux' ); +SELECT VARIABLE_NAME, VARIABLE_VALUE > 0 AS reached +FROM information_schema.SESSION_STATUS +WHERE VARIABLE_NAME LIKE 'SUBQUERY_CACHE%' ORDER BY VARIABLE_NAME; +VARIABLE_NAME reached +SUBQUERY_CACHE_HIT 1 +SUBQUERY_CACHE_MISS 1 +SELECT id, a FROM t1 ORDER BY id; +id a +2 bar +4 quux +5 bar +7 corge +8 bar +11 foo +13 foo +16 foo +DROP TABLE t1, t2; +# +# The same comparison is reached from a correlated subquery, which puts +# the value at a different offset in the same key buffer +# +CREATE TABLE t3 (a VARCHAR(256)) ENGINE=InnoDB; +INSERT INTO t3 VALUES (1),(5),(1),(22),(5),(333),(22),(1); +CREATE TABLE t4 (b INT) ENGINE=InnoDB; +INSERT INTO t4 VALUES (2),(3); +FLUSH STATUS; +SELECT a FROM t3 WHERE EXISTS ( SELECT 1 FROM t4 WHERE b <> t3.a ) ORDER BY a; +a +1 +1 +1 +22 +22 +333 +5 +5 +SELECT VARIABLE_NAME, VARIABLE_VALUE > 0 AS reached +FROM information_schema.SESSION_STATUS +WHERE VARIABLE_NAME LIKE 'SUBQUERY_CACHE%' ORDER BY VARIABLE_NAME; +VARIABLE_NAME reached +SUBQUERY_CACHE_HIT 1 +SUBQUERY_CACHE_MISS 1 +DROP TABLE t3, t4; +# +# Aria leaves the same part of a record undefined, so the outer table's +# engine makes no difference to what is compared +# +CREATE TABLE t5 (id INT PRIMARY KEY, a VARCHAR(256)) ENGINE=Aria; +INSERT INTO t5 VALUES (1,'foo'),(2,'bar'),(3,'foo'),(4,'quux'), +(5,'bar'),(6,'foo'),(7,'corge'),(8,'bar'); +CREATE TABLE t6 (x INT) ENGINE=Aria; +INSERT INTO t6 VALUES (1),(2); +FLUSH STATUS; +UPDATE t5, t6 SET id = id + 100 WHERE a IN ( SELECT 'baz' UNION SELECT 'qux' ); +SELECT VARIABLE_NAME, VARIABLE_VALUE > 0 AS reached +FROM information_schema.SESSION_STATUS +WHERE VARIABLE_NAME LIKE 'SUBQUERY_CACHE%' ORDER BY VARIABLE_NAME; +VARIABLE_NAME reached +SUBQUERY_CACHE_HIT 1 +SUBQUERY_CACHE_MISS 1 +SELECT id, a FROM t5 ORDER BY id; +id a +1 foo +2 bar +3 foo +4 quux +5 bar +6 foo +7 corge +8 bar +DROP TABLE t5, t6; diff --git a/mysql-test/main/subquery_cache_varchar_ref_key.test b/mysql-test/main/subquery_cache_varchar_ref_key.test new file mode 100644 index 0000000000000..e5e2108f3e1f7 --- /dev/null +++ b/mysql-test/main/subquery_cache_varchar_ref_key.test @@ -0,0 +1,88 @@ +--source include/have_innodb.inc + +--echo # +--echo # A ref lookup key built over a VARCHAR is a fixed width image, so the +--echo # bytes that follow the value take part in the key comparison that +--echo # decides whether the previous lookup can be reused, although they +--echo # carry no information. A storage engine is free to leave that part of +--echo # a record undefined, and the subquery cache reaches that comparison +--echo # once per row. Only a build with memory instrumentation can fail on +--echo # the read, so each block below also checks that the subquery cache +--echo # counters moved, which is what shows the comparison was reached at +--echo # all. Only whether they moved is checked, because a prepared +--echo # statement executes the subquery twice and doubles them. +--echo # +--echo # The column has to be wider than 255 bytes. A key field always carries +--echo # a two byte length prefix, and the full width copy that leaves the +--echo # bytes after the value in place is taken only when the source field +--echo # agrees on the width of that prefix. A narrower column takes a +--echo # conversion path that writes the value alone. +--echo # + +CREATE TABLE t1 (id INT PRIMARY KEY, a VARCHAR(256)) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1,'foo'),(2,'bar'),(3,'foo'),(4,'quux'), + (5,'bar'),(6,'foo'),(7,'corge'),(8,'bar'); + +CREATE TABLE t2 (x INT) ENGINE=InnoDB; +INSERT INTO t2 VALUES (1),(2); + +--echo # Values of differing length, so that the bytes after a short one are +--echo # whatever the longer row before it left behind + +--echo # The predicate is false for every row, so nothing is updated +FLUSH STATUS; +UPDATE t1, t2 SET id = id + 100 WHERE a IN ( SELECT 'baz' UNION SELECT 'qux' ); +SELECT VARIABLE_NAME, VARIABLE_VALUE > 0 AS reached +FROM information_schema.SESSION_STATUS +WHERE VARIABLE_NAME LIKE 'SUBQUERY_CACHE%' ORDER BY VARIABLE_NAME; +SELECT id, a FROM t1 ORDER BY id; + +--echo # The same lookups with three rows qualifying +FLUSH STATUS; +UPDATE t1, t2 SET id = id + 10 WHERE a IN ( SELECT 'foo' UNION SELECT 'qux' ); +SELECT VARIABLE_NAME, VARIABLE_VALUE > 0 AS reached +FROM information_schema.SESSION_STATUS +WHERE VARIABLE_NAME LIKE 'SUBQUERY_CACHE%' ORDER BY VARIABLE_NAME; +SELECT id, a FROM t1 ORDER BY id; + +DROP TABLE t1, t2; + +--echo # +--echo # The same comparison is reached from a correlated subquery, which puts +--echo # the value at a different offset in the same key buffer +--echo # + +CREATE TABLE t3 (a VARCHAR(256)) ENGINE=InnoDB; +INSERT INTO t3 VALUES (1),(5),(1),(22),(5),(333),(22),(1); + +CREATE TABLE t4 (b INT) ENGINE=InnoDB; +INSERT INTO t4 VALUES (2),(3); + +FLUSH STATUS; +SELECT a FROM t3 WHERE EXISTS ( SELECT 1 FROM t4 WHERE b <> t3.a ) ORDER BY a; +SELECT VARIABLE_NAME, VARIABLE_VALUE > 0 AS reached +FROM information_schema.SESSION_STATUS +WHERE VARIABLE_NAME LIKE 'SUBQUERY_CACHE%' ORDER BY VARIABLE_NAME; + +DROP TABLE t3, t4; + +--echo # +--echo # Aria leaves the same part of a record undefined, so the outer table's +--echo # engine makes no difference to what is compared +--echo # + +CREATE TABLE t5 (id INT PRIMARY KEY, a VARCHAR(256)) ENGINE=Aria; +INSERT INTO t5 VALUES (1,'foo'),(2,'bar'),(3,'foo'),(4,'quux'), + (5,'bar'),(6,'foo'),(7,'corge'),(8,'bar'); + +CREATE TABLE t6 (x INT) ENGINE=Aria; +INSERT INTO t6 VALUES (1),(2); + +FLUSH STATUS; +UPDATE t5, t6 SET id = id + 100 WHERE a IN ( SELECT 'baz' UNION SELECT 'qux' ); +SELECT VARIABLE_NAME, VARIABLE_VALUE > 0 AS reached +FROM information_schema.SESSION_STATUS +WHERE VARIABLE_NAME LIKE 'SUBQUERY_CACHE%' ORDER BY VARIABLE_NAME; +SELECT id, a FROM t5 ORDER BY id; + +DROP TABLE t5, t6; diff --git a/sql/sql_select.h b/sql/sql_select.h index d03edab058186..7c34664cfd70e 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1986,6 +1986,20 @@ class store_key :public Sql_alloc do_narrow.stop(); + /* + cmp_buffer_with_ref() compares the key buffer as a whole, so the + bytes following a variable length value take part in the comparison + even though they hold no information. Copying the value leaves them + as the source row had them, and a storage engine is free to leave + that part of a record undefined. Their content only decides whether + an already fetched row is fetched again, so mark them defined. + + to_field describes the key part rather than the column: + copy_keys_from_share() narrows the field of a key part built over a + prefix, so this marks the slot in the key buffer and nothing past it. + */ + to_field->mark_unused_memory_as_defined(); + thd->count_cuted_fields= org_count_cuted_fields; return result; }