Skip to content

Commit 400f4f3

Browse files
author
Davi Arnaut
committed
InnoDB wastes 62 of every 16,384 pages in XDES/IBUF_BITMAP extent
The problem is that once the segments of a tablespace are bigger than 32 pages, fragment pages are no longer allocated for use, yet they are still reserved whenever a new fragment extent is allocated. This is a direct consequence of mainly two facts: whenever a new descriptor page is needed (every 16384 pages), the extent that contains the descriptor page cannot be assigned to a segment and is instead used as a fragment extent; and a segment can only allocate up to 32 fragment pages since the array used to track fragment pages belonging to a segment is limited to 32 entries per segment. The solution is to allow for fragment extents to be leased to segments whenever there are free fragment extents available. A fragment extent is considered available if the only used pages in the extent are the extent descriptor and ibuf bitmap pages. A new extent state is used to tag leased extents and to ensure that they are returned to the space free fragment list once no longer being used by a segment. Additionally, a new system variable named innodb_lease_fragment_extents is introduced to control whether free fragment extents are allocated to segments. This is an incompatible change. Once a fragment extent is allocated to a segment, the table that contains the segment is no longer compatible with earlier MySQL versions. Also, the two reserved pages per leased extent are counted towards the size of the segment. This means that index and table size information provided by statements such as SHOW TABLE STATUS will include the size of the two additional pages per leased extent.
1 parent 958b433 commit 400f4f3

File tree

10 files changed

+549
-52
lines changed

10 files changed

+549
-52
lines changed

client/mysqltest.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3052,6 +3052,8 @@ void do_exec(struct st_command *command)
30523052
command->first_argument, ds_res.str);
30533053
}
30543054

3055+
var_set_int("$exec_exit_status", status);
3056+
30553057
DBUG_PRINT("info",
30563058
("error: %d, status: %d", error, status));
30573059

@@ -3078,6 +3080,10 @@ void do_exec(struct st_command *command)
30783080
die("command \"%s\" succeeded - should have failed with errno %d...",
30793081
command->first_argument, command->expected_errors.err[0].code.errnum);
30803082
}
3083+
else
3084+
{
3085+
var_set_int("$exec_exit_status", error);
3086+
}
30813087

30823088
dynstr_free(&ds_cmd);
30833089
DBUG_VOID_RETURN;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
disable_query_log;
2+
disable_result_log;
3+
disable_abort_on_error;
4+
exec innodb_space --help;
5+
enable_abort_on_error;
6+
if ($exec_exit_status != 0)
7+
{
8+
skip Test requires innodb_ruby;
9+
}
10+
enable_result_log;
11+
enable_query_log;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#
2+
# Test that fragment extents can be fully used.
3+
#
4+
5+
# Setup.
6+
SET @old_innodb_file_per_table = @@GLOBAL.innodb_file_per_table;
7+
SET @old_innodb_lease_fragment_extents = @@GLOBAL.innodb_lease_fragment_extents;
8+
SET GLOBAL innodb_file_per_table = ON;
9+
SET GLOBAL innodb_lease_fragment_extents = ON;
10+
CREATE VIEW buffer_pool AS
11+
SELECT CONVERT(VARIABLE_VALUE, UNSIGNED) AS PAGES_DIRTY FROM
12+
INFORMATION_SCHEMA.GLOBAL_STATUS WHERE
13+
VARIABLE_NAME = 'INNODB_BUFFER_POOL_PAGES_DIRTY';
14+
CREATE TABLE t1 (a BIGINT PRIMARY KEY, b VARCHAR(1024), c VARCHAR(1024))
15+
ENGINE=InnoDB;
16+
CREATE PROCEDURE p1(k BIGINT, c BIGINT)
17+
BEGIN
18+
SET autocommit = OFF;
19+
WHILE c > 0 DO
20+
INSERT INTO t1 VALUES (k, REPEAT('b', 1024), REPEAT('c', 1024));
21+
IF (k MOD 1024 = 0) THEN COMMIT; END IF;
22+
SET k = k + 1;
23+
SET c = c - 1;
24+
END WHILE;
25+
SET autocommit = ON;
26+
END|
27+
# Populate table so that more than 16384 pages are used.
28+
CALL p1(0, 130000);
29+
# The free_frag list should now only contain one extent.
30+
start_page bitmap
31+
0 ##########################################################......
32+
# Show that the fragment extent was leased and its pages used.
33+
start end count type
34+
0 0 1 FSP_HDR
35+
1 1 1 IBUF_BITMAP
36+
2 2 1 INODE
37+
3 57 55 INDEX
38+
58 63 6 ALLOCATED
39+
64 16383 16320 INDEX
40+
16384 16384 1 XDES
41+
16385 16385 1 IBUF_BITMAP
42+
16386 18605 2220 INDEX
43+
18606 19455 850 ALLOCATED
44+
# Delete rows so that the fragment extent becomes unused.
45+
DELETE FROM t1 WHERE a >= 114400;
46+
# Show that the fragment extent is returned to the free_frag list.
47+
start_page bitmap
48+
0 ########################################################........
49+
16384 ##..............................................................
50+
# Repopulate table.
51+
CALL p1(114400, 15600);
52+
# Show that the fragment extent can be reused.
53+
start_page bitmap
54+
0 ##########################################################......
55+
# Cleanup.
56+
DROP TABLE t1;
57+
DROP PROCEDURE p1;
58+
DROP VIEW buffer_pool;
59+
SET @@GLOBAL.innodb_file_per_table = @old_innodb_file_per_table;
60+
SET @@GLOBAL.innodb_lease_fragment_extents = @old_innodb_lease_fragment_extents;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
--source include/have_innodb.inc
2+
--source include/have_innodb_ruby.inc
3+
4+
--echo #
5+
--echo # Test that fragment extents can be fully used.
6+
--echo #
7+
--echo
8+
9+
--echo # Setup.
10+
SET @old_innodb_file_per_table = @@GLOBAL.innodb_file_per_table;
11+
SET @old_innodb_lease_fragment_extents = @@GLOBAL.innodb_lease_fragment_extents;
12+
13+
let $MYSQLD_DATADIR= `select @@datadir`;
14+
15+
SET GLOBAL innodb_file_per_table = ON;
16+
SET GLOBAL innodb_lease_fragment_extents = ON;
17+
18+
CREATE VIEW buffer_pool AS
19+
SELECT CONVERT(VARIABLE_VALUE, UNSIGNED) AS PAGES_DIRTY FROM
20+
INFORMATION_SCHEMA.GLOBAL_STATUS WHERE
21+
VARIABLE_NAME = 'INNODB_BUFFER_POOL_PAGES_DIRTY';
22+
23+
CREATE TABLE t1 (a BIGINT PRIMARY KEY, b VARCHAR(1024), c VARCHAR(1024))
24+
ENGINE=InnoDB;
25+
26+
delimiter |;
27+
28+
CREATE PROCEDURE p1(k BIGINT, c BIGINT)
29+
BEGIN
30+
SET autocommit = OFF;
31+
WHILE c > 0 DO
32+
INSERT INTO t1 VALUES (k, REPEAT('b', 1024), REPEAT('c', 1024));
33+
IF (k MOD 1024 = 0) THEN COMMIT; END IF;
34+
SET k = k + 1;
35+
SET c = c - 1;
36+
END WHILE;
37+
SET autocommit = ON;
38+
END|
39+
40+
delimiter ;|
41+
42+
--echo # Populate table so that more than 16384 pages are used.
43+
CALL p1(0, 130000);
44+
45+
# Wait for dirty pages to be flushed to disk.
46+
let $wait_condition = SELECT PAGES_DIRTY = 0 FROM buffer_pool;
47+
--source include/wait_condition.inc
48+
49+
--echo # The free_frag list should now only contain one extent.
50+
--exec innodb_space -f $MYSQLD_DATADIR/test/t1.ibd list-summary -L free_frag
51+
52+
--echo # Show that the fragment extent was leased and its pages used.
53+
--exec innodb_space -f $MYSQLD_DATADIR/test/t1.ibd space-page-type-regions
54+
55+
--echo # Delete rows so that the fragment extent becomes unused.
56+
DELETE FROM t1 WHERE a >= 114400;
57+
58+
# Wait for dirty pages to be flushed to disk.
59+
let $wait_condition = SELECT PAGES_DIRTY = 0 FROM buffer_pool;
60+
--source include/wait_condition.inc
61+
62+
--echo # Show that the fragment extent is returned to the free_frag list.
63+
--exec innodb_space -f $MYSQLD_DATADIR/test/t1.ibd list-summary -L free_frag
64+
65+
--echo # Repopulate table.
66+
CALL p1(114400, 15600);
67+
68+
# Wait for dirty pages to be flushed to disk.
69+
let $wait_condition = SELECT PAGES_DIRTY = 0 FROM buffer_pool;
70+
--source include/wait_condition.inc
71+
72+
--echo # Show that the fragment extent can be reused.
73+
--exec innodb_space -f $MYSQLD_DATADIR/test/t1.ibd list-summary -L free_frag
74+
75+
--echo # Cleanup.
76+
DROP TABLE t1;
77+
DROP PROCEDURE p1;
78+
DROP VIEW buffer_pool;
79+
80+
SET @@GLOBAL.innodb_file_per_table = @old_innodb_file_per_table;
81+
SET @@GLOBAL.innodb_lease_fragment_extents = @old_innodb_lease_fragment_extents;
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
SET @start_global_value = @@global.innodb_lease_fragment_extents;
2+
SELECT @start_global_value;
3+
@start_global_value
4+
0
5+
Valid values are 'ON' and 'OFF'
6+
SELECT @@GLOBAL.innodb_lease_fragment_extents IN (0, 1);
7+
@@GLOBAL.innodb_lease_fragment_extents IN (0, 1)
8+
1
9+
SELECT @@GLOBAL.innodb_lease_fragment_extents;
10+
@@GLOBAL.innodb_lease_fragment_extents
11+
0
12+
SELECT @@SESSION.innodb_lease_fragment_extents;
13+
ERROR HY000: Variable 'innodb_lease_fragment_extents' is a GLOBAL variable
14+
SHOW GLOBAL VARIABLES LIKE 'innodb_lease_fragment_extents';
15+
Variable_name Value
16+
innodb_lease_fragment_extents OFF
17+
SHOW SESSION VARIABLES LIKE 'innodb_lease_fragment_extents';
18+
Variable_name Value
19+
innodb_lease_fragment_extents OFF
20+
SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_lease_fragment_extents';
21+
VARIABLE_NAME VARIABLE_VALUE
22+
INNODB_LEASE_FRAGMENT_EXTENTS OFF
23+
SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_lease_fragment_extents';
24+
VARIABLE_NAME VARIABLE_VALUE
25+
INNODB_LEASE_FRAGMENT_EXTENTS OFF
26+
SET GLOBAL innodb_lease_fragment_extents='OFF';
27+
SELECT @@GLOBAL.innodb_lease_fragment_extents;
28+
@@GLOBAL.innodb_lease_fragment_extents
29+
0
30+
SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_lease_fragment_extents';
31+
VARIABLE_NAME VARIABLE_VALUE
32+
INNODB_LEASE_FRAGMENT_EXTENTS OFF
33+
SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_lease_fragment_extents';
34+
VARIABLE_NAME VARIABLE_VALUE
35+
INNODB_LEASE_FRAGMENT_EXTENTS OFF
36+
SET @@GLOBAL.innodb_lease_fragment_extents=1;
37+
SELECT @@GLOBAL.innodb_lease_fragment_extents;
38+
@@GLOBAL.innodb_lease_fragment_extents
39+
1
40+
SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_lease_fragment_extents';
41+
VARIABLE_NAME VARIABLE_VALUE
42+
INNODB_LEASE_FRAGMENT_EXTENTS ON
43+
SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_lease_fragment_extents';
44+
VARIABLE_NAME VARIABLE_VALUE
45+
INNODB_LEASE_FRAGMENT_EXTENTS ON
46+
SET GLOBAL innodb_lease_fragment_extents=0;
47+
SELECT @@GLOBAL.innodb_lease_fragment_extents;
48+
@@GLOBAL.innodb_lease_fragment_extents
49+
0
50+
SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_lease_fragment_extents';
51+
VARIABLE_NAME VARIABLE_VALUE
52+
INNODB_LEASE_FRAGMENT_EXTENTS OFF
53+
SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_lease_fragment_extents';
54+
VARIABLE_NAME VARIABLE_VALUE
55+
INNODB_LEASE_FRAGMENT_EXTENTS OFF
56+
SET @@GLOBAL.innodb_lease_fragment_extents='ON';
57+
SELECT @@GLOBAL.innodb_lease_fragment_extents;
58+
@@GLOBAL.innodb_lease_fragment_extents
59+
1
60+
SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_lease_fragment_extents';
61+
VARIABLE_NAME VARIABLE_VALUE
62+
INNODB_LEASE_FRAGMENT_EXTENTS ON
63+
SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_lease_fragment_extents';
64+
VARIABLE_NAME VARIABLE_VALUE
65+
INNODB_LEASE_FRAGMENT_EXTENTS ON
66+
SET SESSION innodb_lease_fragment_extents='OFF';
67+
ERROR HY000: Variable 'innodb_lease_fragment_extents' is a GLOBAL variable and should be set with SET GLOBAL
68+
SET @@SESSION.innodb_lease_fragment_extents='ON';
69+
ERROR HY000: Variable 'innodb_lease_fragment_extents' is a GLOBAL variable and should be set with SET GLOBAL
70+
SET GLOBAL innodb_lease_fragment_extents=1.1;
71+
ERROR 42000: Incorrect argument type to variable 'innodb_lease_fragment_extents'
72+
SET GLOBAL innodb_lease_fragment_extents=1e1;
73+
ERROR 42000: Incorrect argument type to variable 'innodb_lease_fragment_extents'
74+
SET GLOBAL innodb_lease_fragment_extents=2;
75+
ERROR 42000: Variable 'innodb_lease_fragment_extents' can't be set to the value of '2'
76+
NOTE: The following should fail with ER_WRONG_VALUE_FOR_VAR (BUG#50643)
77+
SET GLOBAL innodb_lease_fragment_extents=-3;
78+
SELECT @@GLOBAL.innodb_lease_fragment_extents;
79+
@@GLOBAL.innodb_lease_fragment_extents
80+
1
81+
SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_lease_fragment_extents';
82+
VARIABLE_NAME VARIABLE_VALUE
83+
INNODB_LEASE_FRAGMENT_EXTENTS ON
84+
SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_lease_fragment_extents';
85+
VARIABLE_NAME VARIABLE_VALUE
86+
INNODB_LEASE_FRAGMENT_EXTENTS ON
87+
SET GLOBAL innodb_lease_fragment_extents='AUTO';
88+
ERROR 42000: Variable 'innodb_lease_fragment_extents' can't be set to the value of 'AUTO'
89+
SET @@GLOBAL.innodb_lease_fragment_extents = @start_global_value;
90+
SELECT @@GLOBAL.innodb_lease_fragment_extents;
91+
@@GLOBAL.innodb_lease_fragment_extents
92+
0
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
--source include/have_innodb.inc
2+
3+
SET @start_global_value = @@global.innodb_lease_fragment_extents;
4+
SELECT @start_global_value;
5+
6+
#
7+
# Global scope only
8+
#
9+
--echo Valid values are 'ON' and 'OFF'
10+
SELECT @@GLOBAL.innodb_lease_fragment_extents IN (0, 1);
11+
SELECT @@GLOBAL.innodb_lease_fragment_extents;
12+
--error ER_INCORRECT_GLOBAL_LOCAL_VAR
13+
SELECT @@SESSION.innodb_lease_fragment_extents;
14+
SHOW GLOBAL VARIABLES LIKE 'innodb_lease_fragment_extents';
15+
SHOW SESSION VARIABLES LIKE 'innodb_lease_fragment_extents';
16+
SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_lease_fragment_extents';
17+
SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_lease_fragment_extents';
18+
19+
#
20+
# Read-write variable
21+
#
22+
SET GLOBAL innodb_lease_fragment_extents='OFF';
23+
SELECT @@GLOBAL.innodb_lease_fragment_extents;
24+
SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_lease_fragment_extents';
25+
SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_lease_fragment_extents';
26+
SET @@GLOBAL.innodb_lease_fragment_extents=1;
27+
SELECT @@GLOBAL.innodb_lease_fragment_extents;
28+
SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_lease_fragment_extents';
29+
SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_lease_fragment_extents';
30+
SET GLOBAL innodb_lease_fragment_extents=0;
31+
SELECT @@GLOBAL.innodb_lease_fragment_extents;
32+
SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_lease_fragment_extents';
33+
SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_lease_fragment_extents';
34+
SET @@GLOBAL.innodb_lease_fragment_extents='ON';
35+
SELECT @@GLOBAL.innodb_lease_fragment_extents;
36+
SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_lease_fragment_extents';
37+
SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_lease_fragment_extents';
38+
--error ER_GLOBAL_VARIABLE
39+
SET SESSION innodb_lease_fragment_extents='OFF';
40+
--error ER_GLOBAL_VARIABLE
41+
SET @@SESSION.innodb_lease_fragment_extents='ON';
42+
43+
#
44+
# Boolean type.
45+
#
46+
--error ER_WRONG_TYPE_FOR_VAR
47+
SET GLOBAL innodb_lease_fragment_extents=1.1;
48+
--error ER_WRONG_TYPE_FOR_VAR
49+
SET GLOBAL innodb_lease_fragment_extents=1e1;
50+
--error ER_WRONG_VALUE_FOR_VAR
51+
SET GLOBAL innodb_lease_fragment_extents=2;
52+
--echo NOTE: The following should fail with ER_WRONG_VALUE_FOR_VAR (BUG#50643)
53+
SET GLOBAL innodb_lease_fragment_extents=-3;
54+
SELECT @@GLOBAL.innodb_lease_fragment_extents;
55+
SELECT * FROM information_schema.global_variables WHERE variable_name='innodb_lease_fragment_extents';
56+
SELECT * FROM information_schema.session_variables WHERE variable_name='innodb_lease_fragment_extents';
57+
--error ER_WRONG_VALUE_FOR_VAR
58+
SET GLOBAL innodb_lease_fragment_extents='AUTO';
59+
60+
#
61+
# Cleanup
62+
#
63+
64+
SET @@GLOBAL.innodb_lease_fragment_extents = @start_global_value;
65+
SELECT @@GLOBAL.innodb_lease_fragment_extents;

0 commit comments

Comments
 (0)