[MDEV-10649] Optimizer sometimes use "index" instead of "range" access for UPDATE where PK IN (small list of values) Created: 2016-08-23  Updated: 2016-09-28  Resolved: 2016-09-28

Status: Closed
Project: MariaDB Server
Component/s: Storage Engine - InnoDB
Affects Version/s: 10.1.14
Fix Version/s: 10.1.18, 10.0.28

Type: Bug Priority: Blocker
Reporter: Valerii Kravchuk Assignee: Sergei Petrunia
Resolution: Fixed Votes: 1
Labels: upstream

Attachments: File 10.0-debug-printouts.diff     File 5.7-debug-printouts.diff     File csc10649-mysql5.7.15.diff     Text File mdev10649-log.txt     File mdev10649-mysql57.test     File mdev10649-r2.diff     File mdev10649.test     Text File mdev10649.test-output.txt    
Issue Links:
Relates
relates to MDEV-10790 InnoDB statistics update may temporar... Open
Sprint: 10.1.18

 Description   

There is a big table in a statement-based parallel replication setup, like this:

CREATE TABLE `t` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`action` enum('update','create','delete','query') NOT NULL,
`status` enum('unclaimed','claimed','error','completed','paused','cancelled','exemptable','violation','blocked') NOT NULL,
... more columns here ...
`last_change` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`error_message` text CHARACTER SET utf8,
`partition_key` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
... more multiple-column indexes here ...
) ENGINE=InnoDB AUTO_INCREMENT=3184963639 DEFAULT CHARSET=latin1
 
select * from information_schema.tables where TABLE_SCHEMA='test' and TABLE_NAME = 't'\G
*************************** 1. row ***************************
TABLE_CATALOG: def
TABLE_SCHEMA: test
TABLE_NAME: t
TABLE_TYPE: BASE TABLE
ENGINE: InnoDB
VERSION: 10
ROW_FORMAT: Compact
TABLE_ROWS: 271226130
AVG_ROW_LENGTH: 227
DATA_LENGTH: 61576822784
MAX_DATA_LENGTH: 0
INDEX_LENGTH: 93808164864
DATA_FREE: 27502051328
AUTO_INCREMENT: 3184963639
CREATE_TIME: 2016-08-05 13:08:55
UPDATE_TIME: NULL
CHECK_TIME: NULL
TABLE_COLLATION: latin1_swedish_ci
CHECKSUM: NULL
CREATE_OPTIONS:
TABLE_COMMENT:
1 row in set (0.00 sec)

Usually status is updated for several ids in a single UPDATE like this:

mysql> EXPLAIN UPDATE test.t SET status = 'claimed' WHERE id IN (3191531520, 3191531548, 3191675779, 3191675794, 3191849444, 3191879324, 3192120630, 3192120649, 3192120666, 3192120681, 3192120694, 3192120710, 3192120722, 3192120732, 3192120742, 3192120754, 3192120765, 3192152806, 3192177807, 3192252972, 3192284688, 3192284434, 3192284713, 3192284461, 3192284485, 3192284748, 3192284506, 3192284763, 3192284777, 3192284528, 3192284790, 3192284556, 3192284574, 3192284590, 3192284615, 3192284368, 3192284634, 3192284383, 3192284651, 3192284400, 3192284415, 3192284671, 3192440124, 3192570421, 3192570439, 3192570456, 3192570471, 3192570487)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t
type: range
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: NULL
rows: 48
Extra: Using where
1 row in set (0.00 sec)

So, we have range access and reasonable estimation of rows in range. We may get this plan hundreds of times in a row, but sometimes we end up with a totally different plan while this same query is executed by slave:

2339259 system user test Connect 2594 updating UPDATE t SET status = 'claimed' WHERE id IN (3191531520, 3191531548, 3191675779, 3191675794, 3191849444, 3191879324, 3192120630, 3192120649, 3192120666, 3192120681, 3192120694, 3192120710, 3192120722, 3192120732, 3192120742, 3192120754, 3192120765, 3192152806, 3192177807, 3192252972, 3192284688, 3192284434, 3192284713, 3192284461, 3192284485, 3192284748, 3192284506, 3192284763, 3192284777, 3192284528, 3192284790, 3192284556, 3192284574, 3192284590, 3192284615, 3192284368, 3192284634, 3192284383, 3192284651, 3192284400, 3192284415, 3192284671, 3192440124, 3192570421, 3192570439, 3192570456, 3192570471, 3192570487) 0.000
 
mysql> SHOW EXPLAIN FOR 2339259\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t
type: index
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: NULL
rows: 1
Extra: Using where
1 row in set, 1 warning (0.01 sec)

Optimizer used full index scan for the PRIMARY key (and estimated rows as 1), and this caused a very slow execution of the query and millions of locks set in the process that stalled all other parallel replication threads eventually in a "deadlock".

I suspect there is a bug/corner case somewhere in the optimizer when it decides about indexes available for range access. Maybe this is caused by bad InnoDB statistics etc, but in any case I think we should prevent such an execution path.



 Comments   
Comment by Sergei Petrunia [ 2016-09-06 ]

Attached files
10.0-debug-printouts.diff includes

  • Monty's printout patch from the issue
  • Synchronization points from "Elena's testcase"

mdev10649.test

  • "Elena's testcase" with customer info removed.
Comment by Sergei Petrunia [ 2016-09-06 ]

http://lists.askmonty.org/pipermail/commits/2016-September/009777.html (has a small bug, see the patch attached below)

Comment by Sergei Petrunia [ 2016-09-12 ]

The issue is also repeatable on MySQL 5.7.15.
For the reference, adjusted diff and test files: mdev10649-mysql57.test csc10649-mysql5.7.15.diff , test run output: mdev10649-log.txt

Comment by Sergei Petrunia [ 2016-09-12 ]

The adjusted the patch after Monty's review : mdev10649-r2.diff

Generated at Thu Feb 08 07:43:51 UTC 2024 using Jira 8.20.16#820016-sha1:9d11dbea5f4be3d4cc21f03a88dd11d8c8687422.