Skip to content

Commit 0c7ddbd

Browse files
committed
Options, Meta APIs: Optimize get_option by relocating notoptions cache lookup.
In the get_option function, a cache lookup for the notoptions key is performed, which stores an array of keys for options known not to exist. This optimization prevents repeated database queries when certain options are requested. However, the cache lookup for notoptions was conducted before checking if the requested option exists in the cache. Given that it's more likely that the option does exist, this commit reorders the checks to first verify the option's existence in the cache before confirming its absence. This adjustment reduces redundant queries and also eliminates an unnecessary cache lookup, improving overall performance. Props spacedmonkey, costdev, flixos90, azaozz. Fixes #58277. git-svn-id: https://develop.svn.wordpress.org/trunk@56595 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 60d2d8a commit 0c7ddbd

2 files changed

Lines changed: 81 additions & 31 deletions

File tree

src/wp-includes/option.php

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -161,33 +161,6 @@ function get_option( $option, $default_value = false ) {
161161
$passed_default = func_num_args() > 1;
162162

163163
if ( ! wp_installing() ) {
164-
// Prevent non-existent options from triggering multiple queries.
165-
$notoptions = wp_cache_get( 'notoptions', 'options' );
166-
167-
// Prevent non-existent `notoptions` key from triggering multiple key lookups.
168-
if ( ! is_array( $notoptions ) ) {
169-
$notoptions = array();
170-
wp_cache_set( 'notoptions', $notoptions, 'options' );
171-
}
172-
173-
if ( isset( $notoptions[ $option ] ) ) {
174-
/**
175-
* Filters the default value for an option.
176-
*
177-
* The dynamic portion of the hook name, `$option`, refers to the option name.
178-
*
179-
* @since 3.4.0
180-
* @since 4.4.0 The `$option` parameter was added.
181-
* @since 4.7.0 The `$passed_default` parameter was added to distinguish between a `false` value and the default parameter value.
182-
*
183-
* @param mixed $default_value The default value to return if the option does not exist
184-
* in the database.
185-
* @param string $option Option name.
186-
* @param bool $passed_default Was `get_option()` passed a default value?
187-
*/
188-
return apply_filters( "default_option_{$option}", $default_value, $option, $passed_default );
189-
}
190-
191164
$alloptions = wp_load_alloptions();
192165

193166
if ( isset( $alloptions[ $option ] ) ) {
@@ -196,17 +169,38 @@ function get_option( $option, $default_value = false ) {
196169
$value = wp_cache_get( $option, 'options' );
197170

198171
if ( false === $value ) {
172+
// Prevent non-existent options from triggering multiple queries.
173+
$notoptions = wp_cache_get( 'notoptions', 'options' );
174+
175+
// Prevent non-existent `notoptions` key from triggering multiple key lookups.
176+
if ( ! is_array( $notoptions ) ) {
177+
$notoptions = array();
178+
wp_cache_set( 'notoptions', $notoptions, 'options' );
179+
} elseif ( isset( $notoptions[ $option ] ) ) {
180+
/**
181+
* Filters the default value for an option.
182+
*
183+
* The dynamic portion of the hook name, `$option`, refers to the option name.
184+
*
185+
* @since 3.4.0
186+
* @since 4.4.0 The `$option` parameter was added.
187+
* @since 4.7.0 The `$passed_default` parameter was added to distinguish between a `false` value and the default parameter value.
188+
*
189+
* @param mixed $default_value The default value to return if the option does not exist
190+
* in the database.
191+
* @param string $option Option name.
192+
* @param bool $passed_default Was `get_option()` passed a default value?
193+
*/
194+
return apply_filters( "default_option_{$option}", $default_value, $option, $passed_default );
195+
}
196+
199197
$row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) );
200198

201199
// Has to be get_row() instead of get_var() because of funkiness with 0, false, null values.
202200
if ( is_object( $row ) ) {
203201
$value = $row->option_value;
204202
wp_cache_add( $option, $value, 'options' );
205203
} else { // Option does not exist, so we must cache its non-existence.
206-
if ( ! is_array( $notoptions ) ) {
207-
$notoptions = array();
208-
}
209-
210204
$notoptions[ $option ] = true;
211205
wp_cache_set( 'notoptions', $notoptions, 'options' );
212206

tests/phpunit/tests/option/option.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,62 @@ public function test_get_option_should_call_pre_option_filter() {
100100
$this->assertSame( 1, $filter->get_call_count() );
101101
}
102102

103+
/**
104+
* @ticket 58277
105+
*
106+
* @covers ::get_option
107+
*/
108+
public function test_get_option_notoptions_cache() {
109+
$notoptions = array(
110+
'invalid' => true,
111+
);
112+
wp_cache_set( 'notoptions', $notoptions, 'options' );
113+
114+
$before = get_num_queries();
115+
$value = get_option( 'invalid' );
116+
$after = get_num_queries();
117+
118+
$this->assertSame( 0, $after - $before );
119+
}
120+
121+
/**
122+
* @ticket 58277
123+
*
124+
* @covers ::get_option
125+
*/
126+
public function test_get_option_notoptions_set_cache() {
127+
get_option( 'invalid' );
128+
129+
$before = get_num_queries();
130+
$value = get_option( 'invalid' );
131+
$after = get_num_queries();
132+
133+
$notoptions = wp_cache_get( 'notoptions', 'options' );
134+
135+
$this->assertSame( 0, $after - $before, 'The notoptions cache was not hit on the second call to `get_option()`.' );
136+
$this->assertIsArray( $notoptions, 'The notoptions cache should be set.' );
137+
$this->assertArrayHasKey( 'invalid', $notoptions, 'The "invalid" option should be in the notoptions cache.' );
138+
}
139+
140+
/**
141+
* @ticket 58277
142+
*
143+
* @covers ::get_option
144+
*/
145+
public function test_get_option_notoptions_do_not_load_cache() {
146+
add_option( 'foo', 'bar', '', 'no' );
147+
wp_cache_delete( 'notoptions', 'options' );
148+
149+
$before = get_num_queries();
150+
$value = get_option( 'foo' );
151+
$after = get_num_queries();
152+
153+
$notoptions = wp_cache_get( 'notoptions', 'options' );
154+
155+
$this->assertSame( 0, $after - $before, 'The options cache was not hit on the second call to `get_option()`.' );
156+
$this->assertFalse( $notoptions, 'The notoptions cache should not be set.' );
157+
}
158+
103159
/**
104160
* @covers ::get_option
105161
* @covers ::add_option

0 commit comments

Comments
 (0)