Skip to content

Commit b715136

Browse files
author
Venkatesh Prasad
committed
PS-9647: MySQL Perf Improvements
https://perconadev.atlassian.net/browse/PS-9647 Make the following functions inline using the 'always_inline' attribute 1. row_mysql_store_col_in_innobase_format_func() 2. row_mysql_store_col_in_innobase_format() 3. rec_init_offsets_new() 4. Move the definitions of row_mysql_store_col_in_innobase_format() and row_mysql_store_col_in_innobase_format_func() to row0sel.ic NOTE: This forceful inlining increases the binary size by 10 kBs. Before the change (debug): 802135752 After the change (debug): 802146560 Increase in binary size: 10808 bytes
1 parent 2df90d5 commit b715136

File tree

4 files changed

+241
-264
lines changed

4 files changed

+241
-264
lines changed

storage/innobase/include/row0sel.h

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -420,39 +420,6 @@ enum row_sel_match_mode {
420420
of a fixed length column) */
421421
};
422422

423-
/** Stores a non-SQL-NULL field in the MySQL format. The counterpart of this
424-
function is row_mysql_store_col_in_innobase_format() in row0mysql.cc.
425-
@param[in,out] dest buffer where to store; NOTE
426-
that BLOBs are not in themselves stored
427-
here: the caller must allocate and copy
428-
the BLOB into buffer before, and pass
429-
the pointer to the BLOB in 'data'
430-
@param[in] templ MySQL column template. Its following fields
431-
are referenced: type, is_unsigned,
432-
mysql_col_len, mbminlen, mbmaxlen
433-
@param[in] index InnoDB index
434-
@param[in] field_no templ->rec_field_no or templ->clust_rec_field_no
435-
or templ->icp_rec_field_no
436-
@param[in] data data to store
437-
@param[in] len length of the data
438-
@param[in] compress_heap
439-
@param[in] sec_field secondary index field no if the secondary index
440-
record but the prebuilt template is in
441-
clustered index format and used only for end
442-
range comparison. */
443-
void row_sel_field_store_in_mysql_format_func(
444-
byte *dest, const mysql_row_templ_t *templ, const dict_index_t *index,
445-
IF_DEBUG(ulint field_no, ) const byte *data,
446-
ulint len, mem_heap_t **compress_heap IF_DEBUG(, ulint sec_field));
447-
448-
/** Convert a non-SQL-NULL field from Innobase format to MySQL format. */
449-
static inline void row_sel_field_store_in_mysql_format(
450-
byte *dest, const mysql_row_templ_t *templ, const dict_index_t *idx,
451-
ulint field, const byte *src, ulint len, mem_heap_t **compress_heap, ulint sec) {
452-
row_sel_field_store_in_mysql_format_func(
453-
dest, templ, idx, IF_DEBUG(field, ) src, len, compress_heap IF_DEBUG(, sec));
454-
}
455-
456423
/** Search the record present in innodb_table_stats table using
457424
db_name, table_name and fill it in table stats structure.
458425
@param[in] db_name database name

storage/innobase/include/row0sel.ic

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,242 @@ static inline dberr_t row_search_for_mysql(byte *buf, page_cur_mode_t mode,
127127
return (row_search_no_mvcc(buf, mode, prebuilt, match_mode, direction));
128128
}
129129
}
130+
131+
/** Stores a non-SQL-NULL field in the MySQL format. The counterpart of this
132+
function is row_mysql_store_col_in_innobase_format() in row0mysql.cc.
133+
@param[in,out] dest buffer where to store; NOTE
134+
that BLOBs are not in themselves stored
135+
here: the caller must allocate and copy
136+
the BLOB into buffer before, and pass
137+
the pointer to the BLOB in 'data'
138+
@param[in] templ MySQL column template. Its following fields
139+
are referenced: type, is_unsigned,
140+
mysql_col_len, mbminlen, mbmaxlen
141+
@param[in] index InnoDB index
142+
@param[in] field_no templ->rec_field_no or templ->clust_rec_field_no
143+
or templ->icp_rec_field_no
144+
@param[in] data data to store
145+
@param[in] len length of the data
146+
@param[in] compress_heap heap for decompression
147+
@param[in] sec_field secondary index field no if the secondary index
148+
record but the prebuilt template is in
149+
clustered index format and used only for end
150+
range comparison. */
151+
inline MY_ATTRIBUTE((always_inline)) void row_sel_field_store_in_mysql_format_func(
152+
byte *dest, const mysql_row_templ_t *templ, const dict_index_t *index,
153+
IF_DEBUG(ulint field_no, ) const byte *data, ulint len,
154+
mem_heap_t **compress_heap IF_DEBUG(, ulint sec_field)) {
155+
byte *ptr;
156+
#ifdef UNIV_DEBUG
157+
const dict_field_t *field =
158+
templ->is_virtual ? nullptr : index->get_field(field_no);
159+
160+
bool clust_templ_for_sec = (sec_field != ULINT_UNDEFINED);
161+
#endif /* UNIV_DEBUG */
162+
163+
if (templ->is_multi_val) {
164+
ib::fatal(UT_LOCATION_HERE, ER_CONVERT_MULTI_VALUE)
165+
<< "Table name: " << index->table->name
166+
<< " Index name: " << index->name;
167+
}
168+
169+
auto const mysql_col_len = templ->mysql_col_len;
170+
171+
ut_ad(rec_field_not_null_not_add_col_def(len));
172+
UNIV_MEM_ASSERT_RW(data, len);
173+
UNIV_MEM_ASSERT_W(dest, mysql_col_len);
174+
UNIV_MEM_INVALID(dest, mysql_col_len);
175+
176+
switch (templ->type) {
177+
const byte *field_end;
178+
byte *pad;
179+
case DATA_INT:
180+
/* Convert integer data from Innobase to a little-endian
181+
format, sign bit restored to normal */
182+
183+
ptr = dest + len;
184+
185+
for (;;) {
186+
ptr--;
187+
*ptr = *data;
188+
if (ptr == dest) {
189+
break;
190+
}
191+
data++;
192+
}
193+
194+
if (!templ->is_unsigned) {
195+
dest[len - 1] = (byte)(dest[len - 1] ^ 128);
196+
}
197+
198+
ut_ad(mysql_col_len == len);
199+
200+
break;
201+
202+
case DATA_VARCHAR:
203+
case DATA_VARMYSQL:
204+
case DATA_BINARY:
205+
field_end = dest + mysql_col_len;
206+
207+
if (templ->mysql_type == DATA_MYSQL_TRUE_VARCHAR) {
208+
/* If this is a compressed column,
209+
decompress it first */
210+
if (templ->compressed)
211+
data = row_decompress_column(
212+
data, &len,
213+
reinterpret_cast<const byte *>(templ->zip_dict_data.str),
214+
templ->zip_dict_data.length, compress_heap);
215+
216+
/* This is a >= 5.0.3 type true VARCHAR. Store the
217+
length of the data to the first byte or the first
218+
two bytes of dest. */
219+
220+
dest =
221+
row_mysql_store_true_var_len(dest, len, templ->mysql_length_bytes);
222+
/* Copy the actual data. Leave the rest of the
223+
buffer uninitialized. */
224+
memcpy(dest, data, len);
225+
break;
226+
}
227+
228+
/* Copy the actual data */
229+
ut_memcpy(dest, data, len);
230+
231+
/* Pad with trailing spaces. */
232+
233+
pad = dest + len;
234+
235+
ut_ad(templ->mbminlen <= templ->mbmaxlen);
236+
237+
/* We treat some Unicode charset strings specially. */
238+
switch (templ->mbminlen) {
239+
case 4:
240+
/* InnoDB should never have stripped partial
241+
UTF-32 characters. */
242+
ut_a(!(len & 3));
243+
break;
244+
case 2:
245+
/* A space char is two bytes,
246+
0x0020 in UCS2 and UTF-16 */
247+
248+
if (UNIV_UNLIKELY(len & 1)) {
249+
/* A 0x20 has been stripped from the column.
250+
Pad it back. */
251+
252+
if (pad < field_end) {
253+
*pad++ = 0x20;
254+
}
255+
}
256+
}
257+
258+
row_mysql_pad_col(templ->mbminlen, pad, field_end - pad);
259+
break;
260+
261+
case DATA_BLOB:
262+
/* Store a pointer to the BLOB buffer to dest: the BLOB was
263+
already copied to the buffer in row_sel_store_mysql_rec */
264+
265+
row_mysql_store_blob_ref(
266+
dest, mysql_col_len, data, len, templ->compressed,
267+
reinterpret_cast<const byte *>(templ->zip_dict_data.str),
268+
templ->zip_dict_data.length, compress_heap);
269+
break;
270+
271+
case DATA_POINT:
272+
case DATA_VAR_POINT:
273+
case DATA_GEOMETRY:
274+
/* We store all geometry data as BLOB data at server layer. */
275+
row_mysql_store_geometry(dest, mysql_col_len, data, len);
276+
break;
277+
278+
case DATA_MYSQL:
279+
memcpy(dest, data, len);
280+
281+
ut_ad(mysql_col_len >= len);
282+
ut_ad(templ->mbmaxlen >= templ->mbminlen);
283+
284+
/* If field_no equals to templ->icp_rec_field_no, we are examining a row
285+
pointed by "icp_rec_field_no". There is possibility that icp_rec_field_no
286+
refers to a field in a secondary index while templ->rec_field_no points
287+
to field in a primary index. The length should still be equal, unless the
288+
field pointed by icp_rec_field_no has a prefix or this is a virtual
289+
column.
290+
For end range condition check of secondary index with cluster index
291+
template (clust_templ_for_sec), the index column data length (len)
292+
could be smaller than the actual column length (mysql_col_len) if index
293+
is on column prefix. This is not a real issue because the end range check
294+
would only need the prefix part. The length check assert is relaxed for
295+
clust_templ_for_sec. */
296+
ut_ad(templ->is_virtual || templ->mbmaxlen > templ->mbminlen ||
297+
mysql_col_len == len || clust_templ_for_sec ||
298+
(field_no == templ->icp_rec_field_no && field->prefix_len > 0));
299+
300+
/* The following assertion would fail for old tables
301+
containing UTF-8 ENUM columns due to Bug #9526. */
302+
ut_ad(!templ->mbmaxlen || !(mysql_col_len % templ->mbmaxlen));
303+
/* Length of the record will be less in case of
304+
clust_templ_for_sec is true or if it is fetched
305+
from prefix virtual column in virtual index. */
306+
ut_ad(templ->is_virtual || clust_templ_for_sec ||
307+
len * templ->mbmaxlen >= mysql_col_len ||
308+
index->has_row_versions() ||
309+
(field_no == templ->icp_rec_field_no && field->prefix_len > 0) ||
310+
templ->rec_field_is_prefix);
311+
ut_ad(templ->is_virtual || !(field->prefix_len % templ->mbmaxlen));
312+
313+
/* Pad with spaces. This undoes the stripping
314+
done in row0mysql.cc, function
315+
row_mysql_store_col_in_innobase_format(). */
316+
if ((templ->mbminlen == 1 && templ->mbmaxlen != 1) ||
317+
(templ->is_virtual && mysql_col_len > len)) {
318+
/* NOTE: This comment is for the second condition:
319+
This probably comes from a prefix virtual index, where no complete
320+
value can be got because the full virtual column can only be
321+
calculated in server layer for now. Since server now assumes the
322+
returned value should always have padding spaces, thus the fixup.
323+
However, a proper and more efficient solution is that server does
324+
not depend on the trailing spaces to check the terminal of the CHAR
325+
string, because at least in this case,server should know it's a prefix
326+
index search and no complete value would be got. */
327+
memset(dest + len, 0x20, mysql_col_len - len);
328+
}
329+
break;
330+
331+
default:
332+
#ifdef UNIV_DEBUG
333+
case DATA_SYS_CHILD:
334+
case DATA_SYS:
335+
/* These column types should never be shipped to MySQL. */
336+
ut_d(ut_error);
337+
[[fallthrough]];
338+
339+
case DATA_CHAR:
340+
case DATA_FIXBINARY:
341+
case DATA_FLOAT:
342+
case DATA_DOUBLE:
343+
case DATA_DECIMAL:
344+
/* Above are the valid column types for MySQL data. */
345+
#endif /* UNIV_DEBUG */
346+
347+
/* If sec_field value is present then mapping of
348+
secondary index records to clustered index template
349+
happens for end range comparison. So length can
350+
vary according to secondary index record length. */
351+
ut_ad((templ->is_virtual && !field) ||
352+
((field && field->prefix_len)
353+
? field->prefix_len == len
354+
: (clust_templ_for_sec || mysql_col_len == len)));
355+
356+
memcpy(dest, data, len);
357+
}
358+
}
359+
360+
/** Convert a non-SQL-NULL field from Innobase format to MySQL format. */
361+
inline MY_ATTRIBUTE((always_inline)) void row_sel_field_store_in_mysql_format(
362+
byte *dest, const mysql_row_templ_t *templ, const dict_index_t *idx,
363+
ulint field, const byte *src, ulint len, mem_heap_t **compress_heap,
364+
ulint sec) {
365+
row_sel_field_store_in_mysql_format_func(dest, templ, idx,
366+
IF_DEBUG(field, ) src, len,
367+
compress_heap IF_DEBUG(, sec));
368+
}

storage/innobase/rem/rec.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ external tools. */
4747
@param[in] rec physical record
4848
@param[in] index record descriptor
4949
@param[in, out] offsets array of offsets */
50-
static void rec_init_offsets_new(const rec_t *rec, const dict_index_t *index,
51-
ulint *offsets) {
50+
static inline MY_ATTRIBUTE((always_inline)) void rec_init_offsets_new(
51+
const rec_t *rec, const dict_index_t *index, ulint *offsets) {
5252
ulint status = rec_get_status(rec);
5353
ulint n_node_ptr_field = ULINT_UNDEFINED;
5454

0 commit comments

Comments
 (0)