Permalink
Browse files

InnoDB reserves an excessive amount of space in large tables for write

operations.

When performing operations that are expected to expand a table (for
example, allocate new pages due to a page split), InnoDB currently
preallocates and reserves up to 1% of the total size of the tablespace
as a measure to ensure that enough free extents (that is, disk space)
are available for the operation and to ensure that if running out of
disk space, these operations are preemptively failed as to reserve any
remaining free space to operations that end up freeing space (that is,
delete data).

The percentage is reasonable for tables smaller than a few gigabytes,
but not for tables sized at tens of gigabytes or more, at which point
the percentage won't correctly estimate the free space needed to perform
operations and may cause an excessive amount of free extents to be
preallocated. Also, this reservation approach is of dubious need
considering that disk space usage is normally monitored and that
transactions normally won't cause large expansions. In the worst case,
it is even possible to expand the underlying filesystem if the database
goes into a deadlock where it lacks free space for cleaning operations.

The solution is to provide a way to either completely disable free
extents reservation or to control the amount of free extents that are
reserved for such operations. This changes introduces two new system
variables to accomplish both. The variable innodb_reserve_free_extents
can be used to enable or disable free extents reservation and
innodb_free_extents_reservation_factor can be used to control what
percentage of a space size is reserved for operations that may cause
more space to be used.
  • Loading branch information...
1 parent 4a3508f commit 52c63a6cbe815c7a9882914b89e58f8b8e6b41a1 Davi Arnaut committed Jan 3, 2013
@@ -0,0 +1,38 @@
+#
+# Test that free extents reservation can be disabled.
+#
+SET @old_innodb_file_per_table = @@GLOBAL.innodb_file_per_table;
+SET @old_innodb_reserve_free_extents = @@GLOBAL.innodb_reserve_free_extents;
+SET GLOBAL innodb_file_per_table = ON;
+SET GLOBAL innodb_reserve_free_extents = TRUE;
+# Create and populate a table so that new extents are allocated.
+CREATE TABLE t1 (a BIGINT AUTO_INCREMENT PRIMARY KEY, b VARCHAR(512),
+c VARCHAR(512)) ENGINE=InnoDB;
+INSERT INTO t1 VALUES (0, REPEAT('a', 512), REPEAT('b', 512));
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+# Show the number of free extents.
+SELECT (DATA_FREE / 1048576) AS FREE_EXTENTS
+FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 't1';
+FREE_EXTENTS
+4.0000
+# Disable free extents reservation and rebuild the table.
+SET GLOBAL innodb_reserve_free_extents = OFF;
+ALTER TABLE t1 ENGINE=InnoDB;
+# Show the number of free extents.
+SELECT (DATA_FREE / 1048576) AS FREE_EXTENTS
+FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 't1';
+FREE_EXTENTS
+0.0000
+DROP TABLE t1;
+SET @@GLOBAL.innodb_file_per_table = @old_innodb_file_per_table;
+SET @@GLOBAL.innodb_reserve_free_extents = @old_innodb_reserve_free_extents;
@@ -0,0 +1,45 @@
+--source include/have_innodb.inc
+
+--echo #
+--echo # Test that free extents reservation can be disabled.
+--echo #
+
+SET @old_innodb_file_per_table = @@GLOBAL.innodb_file_per_table;
+SET @old_innodb_reserve_free_extents = @@GLOBAL.innodb_reserve_free_extents;
+
+SET GLOBAL innodb_file_per_table = ON;
+SET GLOBAL innodb_reserve_free_extents = TRUE;
+
+--echo # Create and populate a table so that new extents are allocated.
+CREATE TABLE t1 (a BIGINT AUTO_INCREMENT PRIMARY KEY, b VARCHAR(512),
+ c VARCHAR(512)) ENGINE=InnoDB;
+
+INSERT INTO t1 VALUES (0, REPEAT('a', 512), REPEAT('b', 512));
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+INSERT INTO t1 SELECT 0,b,c FROM t1;
+
+--echo # Show the number of free extents.
+SELECT (DATA_FREE / 1048576) AS FREE_EXTENTS
+ FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 't1';
+
+--echo # Disable free extents reservation and rebuild the table.
+SET GLOBAL innodb_reserve_free_extents = OFF;
+ALTER TABLE t1 ENGINE=InnoDB;
+
+--echo # Show the number of free extents.
+SELECT (DATA_FREE / 1048576) AS FREE_EXTENTS
+ FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 't1';
+
+DROP TABLE t1;
+
+SET @@GLOBAL.innodb_file_per_table = @old_innodb_file_per_table;
+SET @@GLOBAL.innodb_reserve_free_extents = @old_innodb_reserve_free_extents;
@@ -0,0 +1,119 @@
+SET @old_innodb_free_extents_reservation_factor = @@global.innodb_free_extents_reservation_factor;
+SELECT @old_innodb_free_extents_reservation_factor;
+@old_innodb_free_extents_reservation_factor
+1
+# Default value
+SET @@global.innodb_free_extents_reservation_factor = 0;
+SET @@global.innodb_free_extents_reservation_factor = DEFAULT;
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+1.000000
+# Scope
+SET innodb_free_extents_reservation_factor = 1;
+ERROR HY000: Variable 'innodb_free_extents_reservation_factor' is a GLOBAL variable and should be set with SET GLOBAL
+SELECT @@innodb_free_extents_reservation_factor;
+@@innodb_free_extents_reservation_factor
+1.000000
+SET GLOBAL innodb_free_extents_reservation_factor = 0;
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+0.000000
+# Min/Max
+SET @@global.innodb_free_extents_reservation_factor = -1;
+Warnings:
+Warning 1292 Truncated incorrect innodb_free_extents_reservation_factor value: '-1'
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+0.000000
+SET @@global.innodb_free_extents_reservation_factor = -0.01;
+Warnings:
+Warning 1292 Truncated incorrect innodb_free_extents_reservation_factor value: '-0.01'
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+0.000000
+SET @@global.innodb_free_extents_reservation_factor = 0.0;
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+0.000000
+SET @@global.innodb_free_extents_reservation_factor = 0.001;
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+0.001000
+SET @@global.innodb_free_extents_reservation_factor = 0.005;
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+0.005000
+SET @@global.innodb_free_extents_reservation_factor = 0.01;
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+0.010000
+SET @@global.innodb_free_extents_reservation_factor = 99;
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+99.000000
+SET @@global.innodb_free_extents_reservation_factor = 99.9;
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+99.900000
+SET @@global.innodb_free_extents_reservation_factor = 100;
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+100.000000
+SET @@global.innodb_free_extents_reservation_factor = 100.1;
+Warnings:
+Warning 1292 Truncated incorrect innodb_free_extents_reservation_factor value: '100.1'
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+100.000000
+SET @@global.innodb_free_extents_reservation_factor = 101;
+Warnings:
+Warning 1292 Truncated incorrect innodb_free_extents_reservation_factor value: '101'
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+100.000000
+# Invalid value
+SET @@global.innodb_free_extents_reservation_factor = -1;
+Warnings:
+Warning 1292 Truncated incorrect innodb_free_extents_reservation_factor value: '-1'
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+0.000000
+SET @@global.innodb_free_extents_reservation_factor = "T";
+ERROR 42000: Incorrect argument type to variable 'innodb_free_extents_reservation_factor'
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+0.000000
+SET @@global.innodb_free_extents_reservation_factor = "Y";
+ERROR 42000: Incorrect argument type to variable 'innodb_free_extents_reservation_factor'
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+0.000000
+SET @@global.innodb_free_extents_reservation_factor = 1001;
+Warnings:
+Warning 1292 Truncated incorrect innodb_free_extents_reservation_factor value: '1001'
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+100.000000
+SET @@global.innodb_free_extents_reservation_factor = OFF;
+ERROR 42000: Incorrect argument type to variable 'innodb_free_extents_reservation_factor'
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+100.000000
+SET @@global.innodb_free_extents_reservation_factor = ON;
+ERROR 42000: Incorrect argument type to variable 'innodb_free_extents_reservation_factor'
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+100.000000
+SET @@global.innodb_free_extents_reservation_factor = TRUE;
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+1.000000
+SET @@global.innodb_free_extents_reservation_factor = FALSE;
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+0.000000
+# Reset
+SET @@global.innodb_free_extents_reservation_factor = @old_innodb_free_extents_reservation_factor;
+SELECT @@global.innodb_free_extents_reservation_factor;
+@@global.innodb_free_extents_reservation_factor
+1.000000
@@ -0,0 +1,92 @@
+SET @start_global_value = @@global.innodb_reserve_free_extents;
+SELECT @start_global_value;
+@start_global_value
+1
+Valid values are 'ON' and 'OFF'
+SELECT @@GLOBAL.innodb_reserve_free_extents IN (0, 1);
+@@GLOBAL.innodb_reserve_free_extents IN (0, 1)
+1
+SELECT @@GLOBAL.innodb_reserve_free_extents;
+@@GLOBAL.innodb_reserve_free_extents
+1
+SELECT @@SESSION.innodb_reserve_free_extents;
+ERROR HY000: Variable 'innodb_reserve_free_extents' is a GLOBAL variable
+SHOW GLOBAL VARIABLES LIKE 'innodb_reserve_free_extents';
+Variable_name Value
+innodb_reserve_free_extents ON
+SHOW SESSION VARIABLES LIKE 'innodb_reserve_free_extents';
+Variable_name Value
+innodb_reserve_free_extents ON
+SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_reserve_free_extents';
+VARIABLE_NAME VARIABLE_VALUE
+INNODB_RESERVE_FREE_EXTENTS ON
+SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_reserve_free_extents';
+VARIABLE_NAME VARIABLE_VALUE
+INNODB_RESERVE_FREE_EXTENTS ON
+SET GLOBAL innodb_reserve_free_extents='OFF';
+SELECT @@GLOBAL.innodb_reserve_free_extents;
+@@GLOBAL.innodb_reserve_free_extents
+0
+SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_reserve_free_extents';
+VARIABLE_NAME VARIABLE_VALUE
+INNODB_RESERVE_FREE_EXTENTS OFF
+SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_reserve_free_extents';
+VARIABLE_NAME VARIABLE_VALUE
+INNODB_RESERVE_FREE_EXTENTS OFF
+SET @@GLOBAL.innodb_reserve_free_extents=1;
+SELECT @@GLOBAL.innodb_reserve_free_extents;
+@@GLOBAL.innodb_reserve_free_extents
+1
+SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_reserve_free_extents';
+VARIABLE_NAME VARIABLE_VALUE
+INNODB_RESERVE_FREE_EXTENTS ON
+SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_reserve_free_extents';
+VARIABLE_NAME VARIABLE_VALUE
+INNODB_RESERVE_FREE_EXTENTS ON
+SET GLOBAL innodb_reserve_free_extents=0;
+SELECT @@GLOBAL.innodb_reserve_free_extents;
+@@GLOBAL.innodb_reserve_free_extents
+0
+SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_reserve_free_extents';
+VARIABLE_NAME VARIABLE_VALUE
+INNODB_RESERVE_FREE_EXTENTS OFF
+SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_reserve_free_extents';
+VARIABLE_NAME VARIABLE_VALUE
+INNODB_RESERVE_FREE_EXTENTS OFF
+SET @@GLOBAL.innodb_reserve_free_extents='ON';
+SELECT @@GLOBAL.innodb_reserve_free_extents;
+@@GLOBAL.innodb_reserve_free_extents
+1
+SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_reserve_free_extents';
+VARIABLE_NAME VARIABLE_VALUE
+INNODB_RESERVE_FREE_EXTENTS ON
+SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_reserve_free_extents';
+VARIABLE_NAME VARIABLE_VALUE
+INNODB_RESERVE_FREE_EXTENTS ON
+SET SESSION innodb_reserve_free_extents='OFF';
+ERROR HY000: Variable 'innodb_reserve_free_extents' is a GLOBAL variable and should be set with SET GLOBAL
+SET @@SESSION.innodb_reserve_free_extents='ON';
+ERROR HY000: Variable 'innodb_reserve_free_extents' is a GLOBAL variable and should be set with SET GLOBAL
+SET GLOBAL innodb_reserve_free_extents=1.1;
+ERROR 42000: Incorrect argument type to variable 'innodb_reserve_free_extents'
+SET GLOBAL innodb_reserve_free_extents=1e1;
+ERROR 42000: Incorrect argument type to variable 'innodb_reserve_free_extents'
+SET GLOBAL innodb_reserve_free_extents=2;
+ERROR 42000: Variable 'innodb_reserve_free_extents' can't be set to the value of '2'
+NOTE: The following should fail with ER_WRONG_VALUE_FOR_VAR (BUG#50643)
+SET GLOBAL innodb_reserve_free_extents=-3;
+SELECT @@GLOBAL.innodb_reserve_free_extents;
+@@GLOBAL.innodb_reserve_free_extents
+1
+SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_reserve_free_extents';
+VARIABLE_NAME VARIABLE_VALUE
+INNODB_RESERVE_FREE_EXTENTS ON
+SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_reserve_free_extents';
+VARIABLE_NAME VARIABLE_VALUE
+INNODB_RESERVE_FREE_EXTENTS ON
+SET GLOBAL innodb_reserve_free_extents='AUTO';
+ERROR 42000: Variable 'innodb_reserve_free_extents' can't be set to the value of 'AUTO'
+SET @@GLOBAL.innodb_reserve_free_extents = @start_global_value;
+SELECT @@GLOBAL.innodb_reserve_free_extents;
+@@GLOBAL.innodb_reserve_free_extents
+1
@@ -0,0 +1,84 @@
+--source include/have_innodb.inc
+
+SET @old_innodb_free_extents_reservation_factor = @@global.innodb_free_extents_reservation_factor;
+SELECT @old_innodb_free_extents_reservation_factor;
+
+--echo # Default value
+SET @@global.innodb_free_extents_reservation_factor = 0;
+SET @@global.innodb_free_extents_reservation_factor = DEFAULT;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+--echo # Scope
+--error ER_GLOBAL_VARIABLE
+SET innodb_free_extents_reservation_factor = 1;
+SELECT @@innodb_free_extents_reservation_factor;
+
+SET GLOBAL innodb_free_extents_reservation_factor = 0;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+--echo # Min/Max
+SET @@global.innodb_free_extents_reservation_factor = -1;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+SET @@global.innodb_free_extents_reservation_factor = -0.01;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+SET @@global.innodb_free_extents_reservation_factor = 0.0;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+SET @@global.innodb_free_extents_reservation_factor = 0.001;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+SET @@global.innodb_free_extents_reservation_factor = 0.005;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+SET @@global.innodb_free_extents_reservation_factor = 0.01;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+SET @@global.innodb_free_extents_reservation_factor = 99;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+SET @@global.innodb_free_extents_reservation_factor = 99.9;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+SET @@global.innodb_free_extents_reservation_factor = 100;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+SET @@global.innodb_free_extents_reservation_factor = 100.1;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+SET @@global.innodb_free_extents_reservation_factor = 101;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+--echo # Invalid value
+SET @@global.innodb_free_extents_reservation_factor = -1;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+--error ER_WRONG_TYPE_FOR_VAR
+SET @@global.innodb_free_extents_reservation_factor = "T";
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+--error ER_WRONG_TYPE_FOR_VAR
+SET @@global.innodb_free_extents_reservation_factor = "Y";
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+SET @@global.innodb_free_extents_reservation_factor = 1001;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+--error ER_WRONG_TYPE_FOR_VAR
+SET @@global.innodb_free_extents_reservation_factor = OFF;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+--error ER_WRONG_TYPE_FOR_VAR
+SET @@global.innodb_free_extents_reservation_factor = ON;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+SET @@global.innodb_free_extents_reservation_factor = TRUE;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+SET @@global.innodb_free_extents_reservation_factor = FALSE;
+SELECT @@global.innodb_free_extents_reservation_factor;
+
+--echo # Reset
+SET @@global.innodb_free_extents_reservation_factor = @old_innodb_free_extents_reservation_factor;
+SELECT @@global.innodb_free_extents_reservation_factor;
Oops, something went wrong.

0 comments on commit 52c63a6

Please sign in to comment.