SQL key length error makes no sense - mysql

The following looks perfectly reasonable to me:
CREATE TABLE `mydb`.`Temp` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`x` VARCHAR ( 300 ) NOT NULL ,
`id_foo` INT NULL DEFAULT NULL,
FOREIGN KEY ( `id_foo`) REFERENCES `Foo` (`id`) ON DELETE CASCADE ,
INDEX (`id_foo`),
INDEX (`x`),
UNIQUE (`id_foo`, `x`)
) ENGINE = INNODB;
With MySQL this gives an error
#1071 - Specified key was too long; max key length is 767 bytes
This seems wrong because the whole row is 309 bytes: less than 767, not even half. What's going on?

According to the MYSQL Documantation : http://dev.mysql.com/doc/refman/5.0/en/create-index.html
MySQL has different limits on the amount of space you can use to define indexes on column(s)
for MyISAM it's 1,000 bytes;
for InnoDB it's 767 .
Moreover, the data type of those columns matters - for VARCHAR, it's 3x
So, an index on a VARCHAR(300) just like in your table will take 900 of those bytes which is greater than 767 bytes, max key length.
EDIT: Apparently this is not a bug of MySQL, but the UTF8 in MySQL that supports up to 3 bytes. Also, with introducing 4-byte utf8 character set (WL#1213) maximum possible key length changed from 255 to 191 characters (191 * 4 + 2 = 766 where 2 bytes hold for the length). All -utf, -utf8mb4, -utf16, -utf32 are affected from this change beginning with the MySQL version 5.5 or higher.

Try determining how long that index needs to be in order to remain effective:
SELECT COUNT(DISTINCT(`x`)) as n_unique,
COUNT(DISTINCT(LEFT(`x`,200))) as n_100,
COUNT(DISTINCT(LEFT(`x`,150))) as n_150,
COUNT(DISTINCT(LEFT(`x`,100))) as n_100,
COUNT(DISTINCT(LEFT(`x`,50))) as n_50,
COUNT(DISTINCT(LEFT(`x`,25))) as n_25,
COUNT(DISTINCT(LEFT(`x`,10))) as n_10
FROM Temp;
Dividing each n_ result by the n_unique will give you the percent coverage. Once you have that you can likely get decent coverage with a smaller number of characters.
ALTER TABLE Temp ADD index x_improved(20)
Where 20 is really the n_ count of distinct variables given above.

It's your INDEX (x) that's the problem. It works if the VARCHAR is shorter.

What character set are you using?
Based on that error, it appears you are using a multi-byte character set, probably utf8, which reserves 3 bytes per character. So a varchar(300) in utf8 results in a 900 byte key length, which exceeds the innodb limit of 767.
In order to create your table with those indexes, you either need to a different character set, or shorten the length of your column. If you just had the index on x I would recommend simply indexing the first 255 characters of that column, but given your unique index that include x that solution is not viable, since it would reject values as duplicates if they match on the first 255 characters, even if they differ in the last 45 characters.
Here are a couple of example that will work:
-- shorten x to 255 characters
CREATE TABLE `mydb`.`Temp` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`x` VARCHAR ( 255 ) NOT NULL ,
`id_foo` INT NULL DEFAULT NULL,
FOREIGN KEY ( `id_foo`) REFERENCES `Foo` (`id`) ON DELETE CASCADE ,
INDEX (`id_foo`),
INDEX (`x`),
UNIQUE (`id_foo`, `x`)
) ENGINE = INNODB;
-- use single-byte character set
CREATE TABLE `mydb`.`Temp` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`x` VARCHAR ( 300 ) NOT NULL ,
`id_foo` INT NULL DEFAULT NULL,
FOREIGN KEY ( `id_foo`) REFERENCES `Foo` (`id`) ON DELETE CASCADE ,
INDEX (`id_foo`),
INDEX (`x`),
UNIQUE (`id_foo`, `x`)
) ENGINE = INNODB DEFAULT CHARSET LATIN1;

Related

Why table's index storage size is bigger after change charset from utf8mb4 to utf8?

Executed: alter table device_msg convert to character set 'utf8' COLLATE 'utf8_unicode_ci';"
As my expect,table data size change to smaller.
But at the same time, table index size change to bigger ?
What happen and why ?
ps: table data size and index size are calculated by information_schema.TABLES
DbEngine: InnoDB
Table Before:
CREATE TABLE `device_msg` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`sn` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`time` datetime(3) NOT NULL,
`msg` json NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `device_UNIQUE` (`sn`,`time`)
) ENGINE=InnoDB AUTO_INCREMENT=62077733 DEFAULT CHARSET=utf8mb4;
Table After:
CREATE TABLE `device_msg` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`sn` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`time` datetime(3) NOT NULL,
`msg` json NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `device_UNIQUE` (`sn`,`time`)
) ENGINE=InnoDB AUTO_INCREMENT=62077733 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Before:
totalSize: 2.14 GB
indexSize: 282.98 MB
dataSize: 1.86 GB
avg_row_len: 297B
After
totalSize: 1.93 GB
indexSize: 413.97 MB
dataSize: 1.52 GB
avg_row_len: 260B
If data of information_schema.TABLES is not accurate,
How to make it right ?
The space taken by utf8mb4, then utf8 (assuming there were no 4-byte characters beforehand) is the same, in spite of the numbers you show.
This ALTER required rebuilding the table and the indexes.
InnoDB structures the data and each secondary index in a BTrees.
Depending on the order by which you insert elements into a BTree, more or fewer "block splits" will occur.
So, You can't really say whether it is the character set change or the rebuild that lead to the index getting bigger and the data getting smaller.
I say it was not the charset change.
Just in my opinion
As I read on MySQL document about the limitation.
https://dev.mysql.com/doc/refman/5.6/en/innodb-restrictions.html
By default, the index key prefix length limit is 767 bytes
if the index column exceeds this size, it will be truncated.
I assume your indexed column value has 255 characters.
in the case of utf8mb4, 1 character = 4 bytes, the limit is around 191 characters.
So 191 characters will be added to index, other (255-191=64) characters will be truncated from the index.
When you change encoding to utf8 (at that time 1 character = 3 bytes), the indexed limit will become around 255 characters.
It means your column value, all 255 characters, will be added to index without truncating.
The characters that are added to the index increased from 191 characters to 255 characters, so the index size was also increased.

How to resolve "specified key was too long max key length is 255 bytes" in mysql?

Whenever i fire this query from one of the mysql client (emma):
CREATE TABLE `tbl_mappings` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`private_id` int(11) unsigned NOT NULL,
`name` tinytext NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`private_id`,`name`(255)),
KEY `FK_tbl__private_integrations_mappings_tbl__private_integrations` (`private_id`),
CONSTRAINT `FK_tbl__private_integrations_mappings_tbl__private_integrations` FOREIGN KEY (`private_id`) REFERENCES `tbl__private_integrations` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
i get error : specified key was too long max key length is 255 bytes
i am using mysql server 5.7,ubuntu 16.04
And i have tried adding configuration in my.cnf under [mysqld] :
innodb_file_format=barracuda
innodb_file_per_table=1
innodb_large_prefix=1
init_connect='SET collation_connection = utf8mb4_unicode_ci'
init_connect='SET NAMES utf8mb4'
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
default-storage-engine=InnoDB
And then restarted mysql service .still it wont work.
Any help is appreciated.
Thank you.
EDIT
Issue appears to be related to the TINYTEXT datatype. (I can replicate the observed behavior with MySQL version 5.7.17-0ubuntu0.16.04.1-log, using either InnoDB or MyISAM.)
The short answer (as a workaround, how to resolve the 1071 warning) is to use datatype VARCHAR(255) in place of TINYTEXT.
I ran several test cases with various character sets (utf8, utf8mb4, latin1) and using InnoDB and MyISAM storage engines. The 1071 warning appears to be related to the prefix length specified in the index on the TINYTEXT column... appears to be a MySQL limit on the prefix length (not specifically related to InnoDB, since I can replicate the behavior with MyISAM.) I did not test with any other TEXT types other than TINYTEXT.
PREVIOUS ANSWER
Index key length limit for InnoDB tables is 767 bytes.
The name(255) in the key definition is specifying the first 255 characters of name. With the MySQL utf8 characterset, a character can take from one to three bytes. And 255 times three is 765. Add in the four bytes for the int private_id, and that's 769, which exceeds the maximum.
That's why you are getting the error.
Several approaches to resolving that.
Easiest would be to reduce the number of characters of name that are included in the index, e.g.
UNIQUE KEY `name` (`private_id`,`name`(254))
If that doesn't satisfy your use case, then you might need to consider using the deprecated innodb_large_prefix setting. You would need to use DYNAMIC or COMPRESSED row format. See the discussions here:
https://dev.mysql.com/doc/refman/5.7/en/innodb-restrictions.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-row-format-specification.html
There are 5 solutions here .
If you are hitting the limit because of trying to use CHARACTER SET utf8mb4. Then do one of the following (each has a drawback) to avoid the error:
⚈ Upgrade to 5.7.7 for 3072 byte limit -- your cloud may not provide this;
⚈ Change 255 to 191 on the VARCHAR -- you lose any keys longer than 191 characters (unlikely?);
⚈ ALTER .. CONVERT TO utf8 -- you lose Emoji and some of Chinese;
⚈ Use a "prefix" index -- you lose some of the performance benefits.
⚈ Stay with 5.6/5.5/10.1 but perform 4 steps to raise the limit to 3072 bytes:
SET GLOBAL innodb_file_format=Barracuda;
SET GLOBAL innodb_file_per_table=1;
SET GLOBAL innodb_large_prefix=1;
logout & login (to get the global values);
ALTER TABLE tbl ROW_FORMAT=DYNAMIC; (or COMPRESSED)
On second glance, don't use TINYTEXT, change to VARCHAR(255) which does not need the prefixing!
On third glance, UNIQUE(x, y(255)) is very likely to be wrong. It says "the combination of x and part of y is unique". It does not say x and all of y is unique.
Fourth... Which version of 5.7? Works fine with 5.7.15:
mysql> CREATE TABLE `tbl_mappings` (
-> `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-> `private_id` int(11) unsigned NOT NULL,
-> `name` tinytext NOT NULL,
-> PRIMARY KEY (`id`),
-> UNIQUE KEY `name` (`private_id`,`name`(255)),
-> KEY `private_id` (`private_id`)
-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8
-> ;
Query OK, 0 rows affected (0.03 sec)
mysql> select ##version;
+-----------+
| ##version |
+-----------+
| 5.7.15 |
+-----------+
1 row in set (0.00 sec)
mysql> SHOW CREATE TABLE tbl_mappings\G
*************************** 1. row ***************************
Table: tbl_mappings
Create Table: CREATE TABLE `tbl_mappings` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`private_id` int(11) unsigned NOT NULL,
`name` tinytext NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`private_id`,`name`(255)),
KEY `private_id` (`private_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.01 sec)

Specified key was too long; max key length is 767 bytes

I understand the InnoDB index max length is 767 bytes.
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(254) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
.....
`token` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`rank` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `user_token_index` (`token`),
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
I want to create a index on my email.
alter table agent add UNIQUE index idx_on_email (email);
But got the error message:
Specified key was too long; max key length is 767 bytes.
But the length of token column only 128 bytes, email is 254 bytes, not above 767 bytes. Hope anyone can help me! Thanks in advance!
varchar(254) when you use utf8mb4, means 254 character and each character has 4 bytes, the email field requires at least 1016 bytes (254 * 4).
you may look at this article:
http://wildlyinaccurate.com/mysql-specified-key-was-too-long-max-key-length-is-767-bytes/
so you can make your email column: varchar(100)
An alternate option would be to reassess the nature and the constraints of the data stored in that table, and how they relate to other data in JOINs, then justify, or not, that a charset and collation of utf8mb4 is needed.
Example: if the data stored and/or compared to other will never have special characters longer then 2 bytes, you may just replace charset and collation with utf8 and utf8_general_ci respectively (or alternate). You may go even shorter for ascii ones.
This assessment / justifying job is a good practice anyway, and may bring accrued performance for free.

SQL Create Table Error - Key is too long?

I am using the following SQL command to create a table, but it's giving the me the following error.
#1071 - Specified key was too long; max key length is 1000 bytes
I check SQL documentation, and it says that "Message: Specified key was too long; max key length is %d bytes" which didn't quite help.
CREATE TABLE Consist_of
(
dssn INTEGER,
pre_no VARCHAR(255),
pname VARCHAR(255),
trade_name VARCHAR(255),
FOREIGN KEY (dssn, pre_no) REFERENCES Prescription(dssn, pre_no),
FOREIGN KEY (pname, trade_name) REFERENCES Drug(pname, trade_name)
);
So as per the error it is clear that you are passing the maximum limit. For MyISAM tables it is 1000 bytes and for InnoDB it is set to 767 bytes.
You can refer to the resolution of this bug reported.
With a character set of (I assume) utf8, a VARCHAR(255) would need 255 x 3 = 765 bytes for its index.
In your case (255+255+255) x 3 = 2295 (for each varchar row).
You can increase the maximum InnoDB index prefix size in MySQL 5.6 to 3072 bytes by setting innodb_large_prefix to YES along with other settings that you'll also need in order to enable that one, discussed here:
http://dev.mysql.com/doc/refman/5.6/en/innodb-parameters.html#sysvar_innodb_large_prefix
This error means that length of index index is more then 1000 bytes. MySQL and storage engines may have this restriction. I have got similar error on MySQL 5.5 - 'Specified key was too long; max key length is 3072 bytes' when ran this script:
CREATE TABLE IF NOT EXISTS example_table1 (
first_col varchar(500) NOT NULL,
second_col varchar(500) NOT NULL,
third_col varchar(500) NOT NULL,
KEY `index` (first_col, second_col, third_col)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
UTF8 is multi-bytes, and key length is calculated in this way - 500 * 3 * 3 = 4500 bytes.
But note, next query works!
CREATE TABLE IF NOT EXISTS example_table1 (
first_col varchar(500) NOT NULL,
second_col varchar(500) NOT NULL,
third_col varchar(500) NOT NULL,
KEY `index` (first_col, second_col, third_col)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

What does size limit on MySQL index mean?

I have a table created like so:
CREATE TABLE `my_table` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`info` varchar(50) DEFAULT NULL,
`some_more_info` smallint(5) unsigned NOT NULL
PRIMARY KEY (`id`),
KEY `my_index` (`some_more_info`,`info`(24)),
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
My question is about the second key called my_index. What does the "(24)" size limit mean? The actual size of the column is 50, but the index is only 24 characters.
Does this mean that MySQL indexes only the first 24 characters of the column info?
In short, yes, the first 24 characters are taken into consideration to build the BTree index. Indexing limits are assigned to text types such as varchar and text, as they don't affect numeric precision.
Yes.
The entire description about the index length can be found here:
http://dev.mysql.com/doc/refman/5.0/en/create-index.html
Prefix lengths are given in characters for nonbinary string types and
in bytes for binary string types. That is, index entries consist of
the first length characters of each column value for CHAR, VARCHAR,
and TEXT columns, and the first length bytes of each column value for
BINARY, VARBINARY, and BLOB columns.
Also you create query has/had some extra ,'s.