Bug #121041 JOIN a table with a condition that does not belong to it
Submitted: 30 Jul 9:21 Modified: 6 Aug 4:28
Reporter: Alex Zimnitski Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Optimizer Severity:S5 (Performance)
Version:8.0.46, 8.4.11, 9.7.2 OS:Linux
Assigned to: CPU Architecture:x86

[30 Jul 9:21] Alex Zimnitski
Description:
A nested loop join occurs when joining a table on a condition that does not belong to that table

The condition check that does not pertain to the joined table should ideally be evaluated first

Impact:
Queries execute 2.5 times slower than anticipated.

A manual selectivity estimation and the use of a scalar subquery are required as a workaround.

Selectivity estimate: table 2 is expected to have very few rows that match the constant JOIN condition.

How to repeat:
DROP TABLE IF EXISTS optimizer_test_1;
CREATE TABLE IF NOT EXISTS optimizer_test_1 (
id bigint UNSIGNED AUTO_INCREMENT PRIMARY KEY,
val_1 double NOT NULL,
v_type tinyint UNSIGNED DEFAULT 0
);

DROP TABLE IF EXISTS optimizer_test_2;
CREATE TABLE IF NOT EXISTS optimizer_test_2 (
id bigint UNSIGNED PRIMARY KEY,
val_2 double NOT NULL
);

DELIMITER $$

CREATE PROCEDURE optimizer_test_generate_data ()
BEGIN
	DECLARE var_id,
			  var_id_limit bigint UNSIGNED;

	SET var_id_limit = 10000000;
	SET var_id = 1;

	START TRANSACTION;

		WHILE var_id < var_id_limit DO

			IF (var_id % 1000) = 0 THEN
				INSERT INTO optimizer_test_1 (id, val_1, v_type)
					VALUES (var_id, RAND(), 2);
				IF RAND() > 0.5 THEN
					INSERT INTO optimizer_test_2 (id, val_2)
						VALUES (var_id, RAND());
				END IF;
			ELSE
				INSERT INTO optimizer_test_1 (id, val_1, v_type)
					VALUES (var_id, RAND(), 0);
			END IF;

			IF (var_id % 10000) = 0 THEN
				COMMIT;
				START TRANSACTION;
			END IF;

			SET var_id = var_id + 1;

		END WHILE;

	COMMIT;

END $$

DELIMITER ;

CALL optimizer_test_generate_data();

SET profiling = 1;
SET profiling = 0;
SHOW PROFILES;

SELECT
	t1.id
	,t1.val_1
	,COALESCE(t2.val_2,0) AS val_2
FROM optimizer_test_1 t1
LEFT JOIN optimizer_test_2 t2 ON t1.v_type = 2 AND t1.id = t2.id # constant where for row !
WHERE t1.v_type IN (0,1,2,3,4) AND t1.val_1>=0.0
;
#12,22708925
#12,23878725
#12,246552

SELECT
	t1.id
	,t1.val_1
	,COALESCE(t2.val_2,0) AS val_2
FROM optimizer_test_1 t1
LEFT JOIN LATERAL (select val_2 from optimizer_test_2 where optimizer_test_2.id = t1.id) AS t2 ON t1.v_type = 2 # constant where for row !
WHERE t1.v_type IN (0,1,2,3,4) AND t1.val_1>=0.0
;
#13,263449
#13,29376725
#13,326489

SELECT
	t1.id
	,t1.val_1
	,COALESCE( IF( t1.v_type = 2, (select val_2 from optimizer_test_2 where optimizer_test_2.id = t1.id) ,0), 0 ) AS val_2 # constant where for row !
FROM optimizer_test_1 t1
WHERE t1.v_type IN (0,1,2,3,4) AND t1.val_1>=0.0
;
#4,9037905
#5,005257
#4,91212775

Suggested fix:
The static condition should be evaluated upfront, before the nested loop begins

Gathering this requires an enhancement to the BNL algorithm
[6 Aug 4:28] Chaithra Marsur Gopala Reddy
Hi Alex Zimnitski,

Thank you for the test case. Verified as described.