Passing parameters in MySQL: IN list vs. temporary table
When you need to pass a list of parameters into a MySQL, there are at least two method to do this:
- Issue a dynamical query which would fill an
IN
list with constant values - Fill a temporary table with these values then use it in a
JOIN
The second method is considered better for many reasons (you can easily reuse the values, it's more injection-proof etc.)
Leaving all these issues aside, let's compare these methods performance-wise.
We will create a table of 50,000 records, pass a list of 500 parameters (using both methods) and see which is faster:
CREATE TABLE filler ( id INT NOT NULL PRIMARY KEY AUTO_INCREMENT ) ENGINE=Memory; CREATE TABLE t_param ( param INT NOT NULL PRIMARY KEY ) ENGINE=Memory; CREATE TABLE t_source ( id INT NOT NULL PRIMARY KEY, data VARCHAR(100) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=UTF8; DELIMITER $$ CREATE PROCEDURE prc_filler(cnt INT) BEGIN DECLARE _cnt INT; SET _cnt = 1; WHILE _cnt <= cnt DO INSERT INTO filler SELECT _cnt; SET _cnt = _cnt + 1; END WHILE; END $$ CREATE PROCEDURE prc_temporary(cnt INT) BEGIN DECLARE _cnt INT; DECLARE _out INT; SET _cnt = 1; WHILE _cnt <= cnt DO TRUNCATE TABLE t_param; PREPARE STMT FROM ' INSERT INTO t_param (param) VALUES (50000), (49900), (49800), (49700), (49600), (49500), (49400), (49300), (49200), (49100), (49000), (48900), (48800), (48700), (48600), (48500), (48400), (48300), (48200), (48100), (48000), (47900), (47800), (47700), (47600), (47500), (47400), (47300), (47200), (47100), (47000), (46900), (46800), (46700), (46600), (46500), (46400), (46300), (46200), (46100), (46000), (45900), (45800), (45700), (45600), (45500), (45400), (45300), (45200), (45100), (45000), (44900), (44800), (44700), (44600), (44500), (44400), (44300), (44200), (44100), (44000), (43900), (43800), (43700), (43600), (43500), (43400), (43300), (43200), (43100), (43000), (42900), (42800), (42700), (42600), (42500), (42400), (42300), (42200), (42100), (42000), (41900), (41800), (41700), (41600), (41500), (41400), (41300), (41200), (41100), (41000), (40900), (40800), (40700), (40600), (40500), (40400), (40300), (40200), (40100), (40000), (39900), (39800), (39700), (39600), (39500), (39400), (39300), (39200), (39100), (39000), (38900), (38800), (38700), (38600), (38500), (38400), (38300), (38200), (38100), (38000), (37900), (37800), (37700), (37600), (37500), (37400), (37300), (37200), (37100), (37000), (36900), (36800), (36700), (36600), (36500), (36400), (36300), (36200), (36100), (36000), (35900), (35800), (35700), (35600), (35500), (35400), (35300), (35200), (35100), (35000), (34900), (34800), (34700), (34600), (34500), (34400), (34300), (34200), (34100), (34000), (33900), (33800), (33700), (33600), (33500), (33400), (33300), (33200), (33100), (33000), (32900), (32800), (32700), (32600), (32500), (32400), (32300), (32200), (32100), (32000), (31900), (31800), (31700), (31600), (31500), (31400), (31300), (31200), (31100), (31000), (30900), (30800), (30700), (30600), (30500), (30400), (30300), (30200), (30100), (30000), (29900), (29800), (29700), (29600), (29500), (29400), (29300), (29200), (29100), (29000), (28900), (28800), (28700), (28600), (28500), (28400), (28300), (28200), (28100), (28000), (27900), (27800), (27700), (27600), (27500), (27400), (27300), (27200), (27100), (27000), (26900), (26800), (26700), (26600), (26500), (26400), (26300), (26200), (26100), (26000), (25900), (25800), (25700), (25600), (25500), (25400), (25300), (25200), (25100), (25000), (24900), (24800), (24700), (24600), (24500), (24400), (24300), (24200), (24100), (24000), (23900), (23800), (23700), (23600), (23500), (23400), (23300), (23200), (23100), (23000), (22900), (22800), (22700), (22600), (22500), (22400), (22300), (22200), (22100), (22000), (21900), (21800), (21700), (21600), (21500), (21400), (21300), (21200), (21100), (21000), (20900), (20800), (20700), (20600), (20500), (20400), (20300), (20200), (20100), (20000), (19900), (19800), (19700), (19600), (19500), (19400), (19300), (19200), (19100), (19000), (18900), (18800), (18700), (18600), (18500), (18400), (18300), (18200), (18100), (18000), (17900), (17800), (17700), (17600), (17500), (17400), (17300), (17200), (17100), (17000), (16900), (16800), (16700), (16600), (16500), (16400), (16300), (16200), (16100), (16000), (15900), (15800), (15700), (15600), (15500), (15400), (15300), (15200), (15100), (15000), (14900), (14800), (14700), (14600), (14500), (14400), (14300), (14200), (14100), (14000), (13900), (13800), (13700), (13600), (13500), (13400), (13300), (13200), (13100), (13000), (12900), (12800), (12700), (12600), (12500), (12400), (12300), (12200), (12100), (12000), (11900), (11800), (11700), (11600), (11500), (11400), (11300), (11200), (11100), (11000), (10900), (10800), (10700), (10600), (10500), (10400), (10300), (10200), (10100), (10000), (9900), (9800), (9700), (9600), (9500), (9400), (9300), (9200), (9100), (9000), (8900), (8800), (8700), (8600), (8500), (8400), (8300), (8200), (8100), (8000), (7900), (7800), (7700), (7600), (7500), (7400), (7300), (7200), (7100), (7000), (6900), (6800), (6700), (6600), (6500), (6400), (6300), (6200), (6100), (6000), (5900), (5800), (5700), (5600), (5500), (5400), (5300), (5200), (5100), (5000), (4900), (4800), (4700), (4600), (4500), (4400), (4300), (4200), (4100), (4000), (3900), (3800), (3700), (3600), (3500), (3400), (3300), (3200), (3100), (3000), (2900), (2800), (2700), (2600), (2500), (2400), (2300), (2200), (2100), (2000), (1900), (1800), (1700), (1600), (1500), (1400), (1300), (1200), (1100), (1000), (900), (800), (700), (600), (500), (400), (300), (200), (100) '; EXECUTE STMT; SELECT SUM(id) INTO _out FROM t_param p JOIN t_source s ON s.id = p.param; DEALLOCATE PREPARE STMT; SET _cnt = _cnt + 1; END WHILE; SELECT _cnt; END; $$ CREATE PROCEDURE prc_list(cnt INT) BEGIN DECLARE _cnt INT; DECLARE _out INT; SET _cnt = 1; WHILE _cnt <= cnt DO PREPARE STMT FROM ' SELECT SUM(id) INTO @out FROM t_source WHERE id IN (50000, 49900, 49800, 49700, 49600, 49500, 49400, 49300, 49200, 49100, 49000, 48900, 48800, 48700, 48600, 48500, 48400, 48300, 48200, 48100, 48000, 47900, 47800, 47700, 47600, 47500, 47400, 47300, 47200, 47100, 47000, 46900, 46800, 46700, 46600, 46500, 46400, 46300, 46200, 46100, 46000, 45900, 45800, 45700, 45600, 45500, 45400, 45300, 45200, 45100, 45000, 44900, 44800, 44700, 44600, 44500, 44400, 44300, 44200, 44100, 44000, 43900, 43800, 43700, 43600, 43500, 43400, 43300, 43200, 43100, 43000, 42900, 42800, 42700, 42600, 42500, 42400, 42300, 42200, 42100, 42000, 41900, 41800, 41700, 41600, 41500, 41400, 41300, 41200, 41100, 41000, 40900, 40800, 40700, 40600, 40500, 40400, 40300, 40200, 40100, 40000, 39900, 39800, 39700, 39600, 39500, 39400, 39300, 39200, 39100, 39000, 38900, 38800, 38700, 38600, 38500, 38400, 38300, 38200, 38100, 38000, 37900, 37800, 37700, 37600, 37500, 37400, 37300, 37200, 37100, 37000, 36900, 36800, 36700, 36600, 36500, 36400, 36300, 36200, 36100, 36000, 35900, 35800, 35700, 35600, 35500, 35400, 35300, 35200, 35100, 35000, 34900, 34800, 34700, 34600, 34500, 34400, 34300, 34200, 34100, 34000, 33900, 33800, 33700, 33600, 33500, 33400, 33300, 33200, 33100, 33000, 32900, 32800, 32700, 32600, 32500, 32400, 32300, 32200, 32100, 32000, 31900, 31800, 31700, 31600, 31500, 31400, 31300, 31200, 31100, 31000, 30900, 30800, 30700, 30600, 30500, 30400, 30300, 30200, 30100, 30000, 29900, 29800, 29700, 29600, 29500, 29400, 29300, 29200, 29100, 29000, 28900, 28800, 28700, 28600, 28500, 28400, 28300, 28200, 28100, 28000, 27900, 27800, 27700, 27600, 27500, 27400, 27300, 27200, 27100, 27000, 26900, 26800, 26700, 26600, 26500, 26400, 26300, 26200, 26100, 26000, 25900, 25800, 25700, 25600, 25500, 25400, 25300, 25200, 25100, 25000, 24900, 24800, 24700, 24600, 24500, 24400, 24300, 24200, 24100, 24000, 23900, 23800, 23700, 23600, 23500, 23400, 23300, 23200, 23100, 23000, 22900, 22800, 22700, 22600, 22500, 22400, 22300, 22200, 22100, 22000, 21900, 21800, 21700, 21600, 21500, 21400, 21300, 21200, 21100, 21000, 20900, 20800, 20700, 20600, 20500, 20400, 20300, 20200, 20100, 20000, 19900, 19800, 19700, 19600, 19500, 19400, 19300, 19200, 19100, 19000, 18900, 18800, 18700, 18600, 18500, 18400, 18300, 18200, 18100, 18000, 17900, 17800, 17700, 17600, 17500, 17400, 17300, 17200, 17100, 17000, 16900, 16800, 16700, 16600, 16500, 16400, 16300, 16200, 16100, 16000, 15900, 15800, 15700, 15600, 15500, 15400, 15300, 15200, 15100, 15000, 14900, 14800, 14700, 14600, 14500, 14400, 14300, 14200, 14100, 14000, 13900, 13800, 13700, 13600, 13500, 13400, 13300, 13200, 13100, 13000, 12900, 12800, 12700, 12600, 12500, 12400, 12300, 12200, 12100, 12000, 11900, 11800, 11700, 11600, 11500, 11400, 11300, 11200, 11100, 11000, 10900, 10800, 10700, 10600, 10500, 10400, 10300, 10200, 10100, 10000, 9900, 9800, 9700, 9600, 9500, 9400, 9300, 9200, 9100, 9000, 8900, 8800, 8700, 8600, 8500, 8400, 8300, 8200, 8100, 8000, 7900, 7800, 7700, 7600, 7500, 7400, 7300, 7200, 7100, 7000, 6900, 6800, 6700, 6600, 6500, 6400, 6300, 6200, 6100, 6000, 5900, 5800, 5700, 5600, 5500, 5400, 5300, 5200, 5100, 5000, 4900, 4800, 4700, 4600, 4500, 4400, 4300, 4200, 4100, 4000, 3900, 3800, 3700, 3600, 3500, 3400, 3300, 3200, 3100, 3000, 2900, 2800, 2700, 2600, 2500, 2400, 2300, 2200, 2100, 2000, 1900, 1800, 1700, 1600, 1500, 1400, 1300, 1200, 1100, 1000, 900, 800, 700, 600, 500, 400, 300, 200, 100) '; EXECUTE STMT; DEALLOCATE PREPARE STMT; SET _cnt = _cnt + 1; END WHILE; SELECT _cnt; END; $$ DELIMITER ; START TRANSACTION; CALL prc_filler(50000); COMMIT; INSERT INTO t_source SELECT id, CONCAT('Value ', id) FROM filler;
We have created two stored procedures.
The first procedure, prc_temporary
, fills the temporary value using a dynamic query then issues the query using a JOIN
in a loop.
The second procedure, prc_range
, just uses the IN
list in a dynamic query, also in a loop.
Let's run both procedures:
CALL prc_list(1000)
_cnt | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1001 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 row fetched in 0.0001s (4.1218s) |
CALL prc_temporary(1000)
_cnt | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1001 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 row fetched in 0.0001s (1.9406s) |
However, if we change the procedures and leave but 20 parameters:
DROP PROCEDURE prc_temporary; DROP PROCEDURE prc_list; CREATE PROCEDURE prc_temporary(cnt INT) BEGIN DECLARE _cnt INT; DECLARE _out INT; SET _cnt = 1; WHILE _cnt <= cnt DO TRUNCATE TABLE t_param; PREPARE STMT FROM ' INSERT INTO t_param (param) VALUES (50000), (49900), (49800), (49700), (49600), (49500), (49400), (49300), (49200), (49100), (49000), (48900), (48800), (48700), (48600), (48500), (48400), (48300), (48200), (48100) '; EXECUTE STMT; SELECT SUM(id) INTO _out FROM t_param p JOIN t_source s ON s.id = p.param; DEALLOCATE PREPARE STMT; SET _cnt = _cnt + 1; END WHILE; SELECT _cnt; END; $$ CREATE PROCEDURE prc_list(cnt INT) BEGIN DECLARE _cnt INT; DECLARE _out INT; SET _cnt = 1; WHILE _cnt <= cnt DO PREPARE STMT FROM ' SELECT SUM(id) INTO @out FROM t_source WHERE id IN (50000, 49900, 49800, 49700, 49600, 49500, 49400, 49300, 49200, 49100, 49000, 48900, 48800, 48700, 48600, 48500, 48400, 48300, 48200, 48100) '; EXECUTE STMT; DEALLOCATE PREPARE STMT; SET _cnt = _cnt + 1; END WHILE; SELECT _cnt; END; $$
, both procedures have almost the same performance:
CALL prc_list(1000)
_cnt | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1001 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 row fetched in 0.0001s (0.6315s) |
CALL prc_temporary(1000)
_cnt | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1001 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 row fetched in 0.0001s (0.6408s) |
We see that for a large list of parameters, passing them in a temporary table is much faster that as a constant list, while for small lists performance is almost the same.
Using a temporary table is the best way to pass large arrays of parameters in MySQL.
Written by Quassnoi
August 18th, 2009 at 11:00 pm
Posted in MySQL
7 Responses to 'Passing parameters in MySQL: IN list vs. temporary table'
Subscribe to comments with RSS
You could provide an SQL example
Alon
24 Apr 13 at 15:51
@Alon: click on the links (“Table creation details”) and (“Changing procedures here”)
Quassnoi
24 Apr 13 at 16:03
Isn’t mysql internally creating a temp table even with IN (LIST) syntax? If you perform an EXPLAIN on any query with the IN list statement, you will see the “using temporary” message. Based on that I would think there has to be more to this performance difference. Perhaps the temp table that Mysql is using also has a unique index on the values, which would avoid redundant / duplicate joins and save on query time but lose on time to create the unique temp table values. It would be interesting to see the results of your test if you also added unique indexing on your temp table. I would think the methods would be on par at that point.
Mike
30 Jun 13 at 12:03
Not sure where is a bottleneck in your procedure,
but IN (with,constants, here) may not be a culprit.
Maybe indexes?
I just run a query with some 2100 constants (yes, 2 zeros – all integers) on a temp table 11.000 rows and it took some 0.4 sec to complete.
I must add, that query had 7 tables involved 2 of which were LEFT JOINed and it was quite heavy with filters: multiple BETWEENs,”=”, even some SELECT GROUP_CONCAT() subqueries.
On top of above, query run BOOLEAN FULLTEXT search on 2 tables, which is not speediest of them all.
0.4 sec is not that bad.
Jeff Parr
18 Oct 13 at 23:53
Does this still hold true 8 years later?
Yang
10 Nov 17 at 16:13
Honestly I don’t know but there are scripts in the article which make it easy to set up the same experiment on a newer version of MySQL.
Why don’t you run them and share your findings with us?
Quassnoi
14 Nov 17 at 14:02
Not true on mariadb 10.4.22 for example, probably not with current mysql versions either.
CALL prc_list(1000);
is at least 6 times faster than
CALL prc_temporary(1000);
when testing with 500 parameters.
Heihei
9 Feb 22 at 16:36