MySQL: column size limit - mysql

I'm currently working on a Windows OS and I have installed MySQL community server 5.6.30 and everything is fine. I have a script that initializes the DB and again, everything works fine.
Now I'm trying to run this script on a Linux environment -- same MySQL version -- and I get the following error:
ERROR 1074 (42000) at line 3: Column length too big for column
'txt' (max = 21845); use BLOB or TEXT instead
Script -
DROP TABLE IF EXISTS text;
CREATE TABLE `texts` (
`id` BINARY(16) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
`txt` VARCHAR(50000) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
Obviously there's some MySQL server configuration on my Windows OS that I need to replicate on Linux; can anyone share an ideas?
Update 1
on AWS's RDS it also works and im pretty sure its just a service on top of linux so obviously its just a config issue.
does any body knows how to reach varchar 50k with UTF8 ?. i dont want to use TEXT or MEDIUMTEXT or any else , just plain old varchar(size)
Update 2
i appreciate the different solutions that were suggested but im not looking for a new solution im only looking for an answer as do why varchar(50k) works under windows and under linux it doesnt.
Btw , im using charcter set UTF8 and collation utf8_general_ci .
Answer
to answer my own question , it was an issue with the SQL_MODE it was set to
STRICT_TRANS_TABLES and should have been removed.

According to the documentation:
Although InnoDB supports row sizes larger than 65,535 bytes
internally, MySQL itself imposes a row-size limit of 65,535 for the
combined size of all columns:
mysql> CREATE TABLE t (a VARCHAR(8000), b VARCHAR(10000),
-> c VARCHAR(10000), d VARCHAR(10000), e VARCHAR(10000),
-> f VARCHAR(10000), g VARCHAR(10000)) ENGINE=InnoDB;
ERROR 1118 (42000): Row size too large. The maximum row size for the
used table type, not counting BLOBs, is 65535. You have to change some
columns to TEXT or BLOBs
(Unfortunately, this example does not provide the character set so we don't really know how large the columns are.)
The utf8 encoding uses 1, 2, or 3 bytes per character. So, the maximum number of characters that can safely fit in a page of 65,535 bytes (the MySQL maximum) is 21,845 characters (21,845*3 = 65,535).
Despite the versions being similar, it would appear the Windows is being conservative in its space allocation and guaranteeing that you can store any characters in the field. Linux seems to have a more laissez-faire attitude. You can store some strings with over 21,845 characters, depending on the characters.
I have no idea why this difference would exist in the same version. Both methods are "right" in some sense. There are simple enough work-arounds:
Use TEXT.
Switch to a collation that has shorter characters (which is presumably what you want to store).
Reduce the size of the field.

please simply use TEXT to declare txt column
DROP TABLE IF EXISTS text;
CREATE TABLE `texts` (
`id` BINARY(16) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
`txt` TEXT DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

utf8 needs up to 3 bytes per character. utf8mb4: 4; latin1: 1; ascii: 1; etc. VARCHAR(N) is implemented as a 1- or 2-byte length in front of the bytes for the text. That is allowed to hold N characters (not bytes). So, if you say you want utf8, then 3*N must be less than 65535, the max value for a 2-byte length.
Be glad you are not running in some old version, where VARCHAR had a limit of 255.
If your txt does not need characters other than ascii or English, then use CHARACTER SET latin1.
In InnoDB, when there are 'long' fields (big varchars, texts, blobs, etc), some or all of the column is stored in a separate block(s). There is a limit of about 8000 bytes for what is stored together in the record.
If you really need 50K of utf8, then MEDIUMTEXT is what you need. It uses a 3-byte length and can hold up to 16M bytes (5M characters, possibly more, since utf8 is a variable length encoding).
Most applications can (should?) use either ascii (1 byte per character) or utf8mb4 (1-4 bytes per character). The latter allows for all languages, including Emoji and the 4-byte Chinese characters that utf8 cannot handle.
As for why Windows and Linux work differently here, I don't know. Are you using the same version? Suggest you file a bug report with http://bugs.mysql.com . (And provide a link to it from this Question.)

If you absolutely must use varchar - which is a bad solution to this problem! - then here's something you can try:
CREATE TABLE `texts` (
`id` BINARY(16) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
`txt` VARCHAR(20000) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
CREATE TABLE `texts2` (
`id` BINARY(16) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
`txt` VARCHAR(20000) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
CREATE TABLE `texts3` (
`id` BINARY(16) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
`txt` VARCHAR(10000) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
There's 50000 characters. Now your client application will have to manage breaking up the text into the separate chunks, and creating the records in each table. Likewise reading the text back in will require you to do 3 select statements, but you will then have 50000 characters.
It's just not at all recommended to do this with any database implementation.
I've worked in a few environments where large text was stored in columns in the database, and it always wound up causing more problems than it solved.
These should really be spooled to files on disk, and a reference to the full path to the file stored in the database.
Then run some indexing engine over this corpus of documents.
you will get greater scalability from this, and easier management.

Just to add for more clarity. If you are using a solution that definitely requires a long VarChar. Like in my case when trying to configure WatchDog.NET to use mysql database for a .NET web api log.
You can sign into mysql database as root user and then run:
SET GLOBAL sql_mode = ""

Related

(42000) Row size too large, from an Oracle dump to a MySQL dump

I've seen many threads about this error, but the solutions I've found don't seem to be applicable in my case.
I've received a rather large (~150Go) dump file from an Oracle database.
I converted it to a MySQL one, using OraDump. However, when I try to import it in my MySQL server, I get the infamous error :
ERROR 111 (42000) at line 162936 : Row size too large. The maximum row size for the used table, not counting BLOBs, is 65535.
This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs.
I tried increasing the innodb_log_file_size parameter, removing the strict mode, switching from ImmoDB to myISAM, nothing worked.
In my last attempt, I tried to add the -f parameter to the dump importation, in hope to just squeeze past the error, but now it just seems stuck.
I don't think I can change the table schemas, since they are created within the 150Go dump file, and I don't even know which tables/columns are at fault.
Is there any way around it ?
EDIT : I managed to find the table responsible for that error, and found that it happens when I'm trying to declare it :
#
# Table structure for table 'F_TABLE_EXAMPLE'
#
DROP TABLE IF EXISTS `F_TABLE_EXAMPLE`;
CREATE TABLE `F_TABLE_EXAMPLE` (
`COL_1` BIGINT,
`COL_2` VARCHAR(10) CHARACTER SET utf8,
`COL_3` BIGINT,
`COL_4` BIGINT,
`COL_5` DECIMAL(16,2),
`COL_6` DECIMAL(16,2),
`COL_7` VARCHAR(5) CHARACTER SET utf8,
`COL_8` DATETIME,
`COL_9` VARCHAR(50) CHARACTER SET utf8,
`COL_10` VARCHAR(4000) CHARACTER SET utf8,
`COL_11` VARCHAR(4000) CHARACTER SET utf8,
`COL_12` VARCHAR(4000) CHARACTER SET utf8,
`COL_13` VARCHAR(4000) CHARACTER SET utf8,
`COL_14` VARCHAR(4000) CHARACTER SET utf8,
`COL_15` VARCHAR(4000) CHARACTER SET utf8
) ENGINE=InnoDB;
If I remove COL_15, there's no error, but with it included I get the usual error. (I only included COL_15 since the error begins there, but I have a bunch of other columns in my declaration)
The way to figure out which table is at fault is to dump and restore one table at a time. That will identify which table(s) are at fault. Then if necessary, dump and restore smaller subsets of rows from the table that causes the error. Continue subdividing the problem until you have narrowed it down to the culprit.
This process might take several tries.
Don't change the storage engine. This particular error is about MySQL in general, not any specific storage engine.
This error has nothing to do with the innodb_log_file_size. (Once you get past this error, you might get a subsequent error regarding innodb_log_file_size — it must be 10x the size of the largest individual BLOB or TEXT value).
Don't try to squeeze past the error by "forcing" it or disabling strict mode. If it did allow this, it would just corrupt your data.
The error message tells you exactly what you need to do:
You have to change some columns to TEXT or BLOBs.
This means change the table definition in MySQL. You probably don't have to change it in Oracle.
If you can't change the table definition, then sorry, you can't import this data into MySQL.
If you want any suggestion for how to fix this, then please post the DDL for the original table from Oracle, and the DDL for the table you're trying to import into in MySQL. Please use DBMS_METADATA.GET_DDL() in Oracle and SHOW CREATE TABLE in MySQL to get the DDL in text — not a screenshot image.
I see from your table definition that you have six columns: VARCHAR(4000) CHARACTER SET utf8. The potential width of these columns is 72000 bytes, because MySQL's utf8 character set may store up to 3 bytes per character. These columns therefore exceed the row size limit. When I test it, I get the same error you did as I try to create the table.
If I change each of these six VARCHAR(4000) columns to TEXT in my test, the table is created successfully. This is what the error message suggested to do.
CREATE TABLE `F_TABLE_EXAMPLE` (
`COL_1` BIGINT,
`COL_2` VARCHAR(10) CHARACTER SET utf8,
`COL_3` BIGINT,
`COL_4` BIGINT,
`COL_5` DECIMAL(16,2),
`COL_6` DECIMAL(16,2),
`COL_7` VARCHAR(5) CHARACTER SET utf8,
`COL_8` DATETIME,
`COL_9` VARCHAR(50) CHARACTER SET utf8,
`COL_10` TEXT CHARACTER SET utf8,
`COL_11` TEXT CHARACTER SET utf8,
`COL_12` TEXT CHARACTER SET utf8,
`COL_13` TEXT CHARACTER SET utf8,
`COL_14` TEXT CHARACTER SET utf8,
`COL_15` TEXT CHARACTER SET utf8
) ENGINE=InnoDB;
I would also recommend to use character set utf8mb4 in all cases. This is the default in MySQL 8.0, and it supports the full range of utf8 encodings.
Seeing the CREATE TABLE for the offending table may suffice for figuring out what to do next.
If the loading loaded some, but not all, of the tables, can you figure out which table was "last" (and not fully reloaded) or "next" to be reloaded?
The error talked about "row size" the talk about BLOB, etc may be not that relevant, since there is a datatype for a column. In particular, changing columns in an Engine=InnoDB table from VARCHAR to TEXT will not help.
The problem may be in having too many columns and/or a "row format" that is less forgiving of lots of columns.
More on limits: http://mysql.rjweb.org/doc.php/limits
More
It appears that "line 162936" is bigger than most. For loading large rows...
Check the current values of net_buffer_length and max_allowed_packet, then... Increase net_buffer_length and max_allowed_packet in the config file (or when invoking the server).
Change
VARCHAR(4000) CHARACTER SET utf8
to
TEXT CHARACTER SET utf8
for each of those big columns. The functionality will be the same, while avoiding the error. (Technically, incoming text will not be limited to 4000 characters, but instead limited to 64K bytes.)
Since you seem to have a big dump file, and very few editors can handle files that big, and the dump replaces the table, I do not have an easy way to achieve the edit. If you could rebuild the dump, but without the table creations, then you could create the tables manually before the load.
mysql> CREATE TABLE `F_TABLE_EXAMPLE` (
-> `COL_1` BIGINT,
-> `COL_2` VARCHAR(10) CHARACTER SET utf8,
-> `COL_3` BIGINT,
-> `COL_4` BIGINT,
-> `COL_5` DECIMAL(16,2),
-> `COL_6` DECIMAL(16,2),
-> `COL_7` VARCHAR(5) CHARACTER SET utf8,
-> `COL_8` DATETIME,
-> `COL_9` VARCHAR(50) CHARACTER SET utf8,
-> `COL_10` TEXT CHARACTER SET utf8,
-> `COL_11` TEXT CHARACTER SET utf8,
-> `COL_12` TEXT CHARACTER SET utf8,
-> `COL_13` TEXT CHARACTER SET utf8,
-> `COL_14` TEXT CHARACTER SET utf8,
-> `COL_15` TEXT CHARACTER SET utf8
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected, 9 warnings (0.07 sec)
mysql> show warnings;
+---------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 3719 | 'utf8' is currently an alias for the character set UTF8MB3, but will be an alias for UTF8MB4 in a future release. Please consider using UTF8MB4 in order to be unambiguous. |
The Warnings are suggesting that you might want to use utf8mb4 -- which lets you get the rest of Chinese and Emoji.

MySQL string matching using dashes

I am currently migrating data from MySQL 5.6.41 on Windows, to MySQL 8.0.21 on Windows. Overall, a pretty smooth migration, with a couple of very frustrating hiccups. There's one table that looks like this:
CREATE TABLE `domains` (
`intDomainID` int(11) NOT NULL AUTO_INCREMENT,
`txtDomain` varchar(100) NOT NULL,
`dtDateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`blnTor` int(1) NOT NULL DEFAULT '0',
`txtTLD` varchar(100) NOT NULL,
PRIMARY KEY (`intDomainID`),
UNIQUE KEY `txtDomain` (`txtDomain`)
ENGINE=InnoDB AUTO_INCREMENT=10127897 DEFAULT CHARSET=utf8mb4;
The CREATE SCHEMA is complete, and was created by Workbench's "Copy to Clipboard" --> "Create Schema" function.
When I used the built in Workbench export/import, the import always failed with "Duplicate value in txtDomain" (paraphrasing here) error, which is weird because the original table has a UNIQUE KEY constraint on that field, so there cannot be duplicates, and I confirmed, the values it was finding as duplicates were NOT duplicates in the original database.
I then dumped the table using SELECT ... INTO OUTFILE, moved the file over to the new server, and did a LOAD DATE INFILE. This also failed with the same "Duplicate value in txtDomain" error.
I then removed the UNIQUE KEY constraint, and redid the LOAD DATE INFILE. This worked, the data is there. However, I cannot add back the UNIQUE KEY constraint due to "duplicates". I investigated and found this:
Query result on MySQL 5.6.41:
Query result on MySQL 8.0.21:
Now, what is going on? The table definition, the database, table and field charset/collations are identical. I need that UNIQUE KEY constraint back...
Why is http://d­­eepdot35wv­­m­eyd5.onion:80 == http://d­­ee-p---dot35w-v­­m­eyd5.onion:80 ??
In case it helps, my export command:
SELECT * INTO OUTFILE 'S:\\temp\\domains.txt'
FIELDS TERMINATED BY ','
OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\r\n'
FROM domains;
My import command:
LOAD DATA INFILE 'E:\\DB Backup\\ServerMigration\\domains.txt'
INTO TABLE domains
FIELDS TERMINATED BY ','
OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\r\n';
COLLATIONS:
Collations Old Server: utf8_general_ci [I don't remember touching this value]
New Server: utf8mb4_0900_ai_ci [I didn't touch this value]
DB old/new are the same: utf8mb4_0900_ai_ci
Table old/new are the same: utf8mb4_0900_ai_ci
This is how the raw TXT file looks like on the file-system:
Note, if I paste in one of the URLs from the screenshot into here, it magically turns into the "correct" value, without the dashes:
i.e.: http://deepdot35w­­v­­­m­­­eyd5.onion:80
Note2: Using Notepad++, if I convert a regular "dash" to HEX I get "2D". However, if I convert one from the URLs that's causing trouble, I get HEX "C2AD". So it seems that I'm dealing with a weird unicode character and not a dash?
Note3: If anyone wants a small sample file, here it is:
https://www.dropbox.com/s/1ssbl95t2jgn2xy/domains_small.zip
The character in question is U+00AD "SOFT HYPHEN" - a non-printable character that is used to signal a possible hyphenation point inside a word.
It seems that the COLLATION used handles these characters differently on the new setup (MySQL 8.0 with default collation settings) than it did on the old setup (MySQL 5.7 with default collation settings):
These nonprintable characters are now ignored in a comparison.
You can test the difference with this simple fiddle. The comparison is "0" in 5.6, but "1" in MySQL 8.0 -> https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=a9bd0bf7de815dc14a886c5069bd1a0f
Note that the SQL fiddle also uses a default collation configuration when it's not specified explicitly.
You might fix that by setting a binary UTF-8 collation for the txtDomain column, which is actually what you want for technical strings anyway:
CREATE TABLE `domains` (
`intDomainID` int(11) NOT NULL AUTO_INCREMENT,
`txtDomain` varchar(100) NOT NULL
CHARACTER SET utf8mb4
COLLATE utf8mb4_binary_ci,
`dtDateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`blnTor` int(1) NOT NULL DEFAULT '0',
`txtTLD` varchar(100) NOT NULL,
PRIMARY KEY (`intDomainID`),
UNIQUE KEY `txtDomain` (`txtDomain`)
) ENGINE=InnoDB AUTO_INCREMENT=10127897 DEFAULT CHARSET=utf8mb4;
UPDATE: As it turns out, the COLLATION must have been different between the old (5.6) and new (8.0) setup, as utf8mb4_0900_ai_ci was introduced with MySQL 8.0. The old collation must have been utf8mb4_general_ci, which when applied shows the desired behaviour in MySQL 8.0, too.
But still, you should use binary collation for technical strings like URLs anyways.

Json to mariadb triples in storage size

I am trying to move my file based organizing json files to mariadb. Approximately there are 2,000,000 json files where in my file based system are zipped. The total storage space for the zipped json files is 7GB.
When i inserted all the records to Mariadb the table storage became 35GB.
i altered my table to be compress and the table size is 15GB.
Is there a way to reduce even more the table size?
Is it normal for the storage to double when data is added to mariadb?
this is my table
CREATE TABLE `sbpi_json` (
`fileid` int(11) NOT NULL,
`json_data` longtext COLLATE utf8_bin NOT NULL,
`idhash` char(32) COLLATE utf8_bin NOT NULL,
`sbpi` int(15) NOT NULL,
`district` int(2) NOT NULL,
`index_val` int(2) NOT NULL,
`updated` text COLLATE utf8_bin NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=COMPRESSED;
ALTER TABLE `sbpi_json`
ADD PRIMARY KEY (`fileid`),
ADD UNIQUE KEY `idhash` (`idhash`),
ADD KEY `sbpi` (`sbpi`);
The JSON column in question is json_data, correct? It averages (uncompressed) about 10KB, correct? In the file implementation, there are multiple 'versions' of each, correct? If so, how do you tell which one you want to deliver to the user?
Most compression techniques give you 3:1; InnoDB compression gives you 2:1. This is partially because it has things that it can't (or won't) compress.
Compressing just the JSON column (in client code) and storing it in a MEDIUMBLOB will probably take less space in InnoDB than using COMPRESSED. (But this will not be a huge savings.)
Focus on how you pick which 'version' of the JSON do deliver to the user. Optimize the schema around that. Then decide on how to store the data.
Given that the table can efficiently say which file contains the desired JSON, then that will be the best approach. And use some normal, fast-to-uncompress technique; don't focus on maximal-compression.
If char(32) COLLATE utf8_bin is a hex string, use ascii, not utf8.
If it is hex, then UNHEX to further shrink it to only BINARY(16).
When a row is bigger than 8KB, some of the data (probably json_data) is stored "off-record". This implies an extra disk access and disk allocation is a bit more sloppy. Hence, storing that column as a file ends up taking about the same amount of time and space.
The OS probably allocates space in 4KB chunks. InnoDB uses 16KB blocks.
It's the text type that takes too much space.
You can try to replace it with a smaller variant of text type if you can give for granted that that much lenght is ok.
Also replacing char(32) with varchar(32) will help if those values are not always full lenght.
Or you can go with varchar even for the textual field, but keep eyes on what's on this answer before doing so.
Hope I helped!

Specified key was too long; max key length is 767 MariaDB 10.1 [duplicate]

When I executed the following command:
ALTER TABLE `mytable` ADD UNIQUE (
`column1` ,
`column2`
);
I got this error message:
#1071 - Specified key was too long; max key length is 767 bytes
Information about column1 and column2:
column1 varchar(20) utf8_general_ci
column2 varchar(500) utf8_general_ci
I think varchar(20) only requires 21 bytes while varchar(500) only requires 501 bytes. So the total bytes are 522, less than 767. So why did I get the error message?
#1071 - Specified key was too long; max key length is 767 bytes
767 bytes in MySQL version 5.6 (and prior versions), is the stated prefix limitation for InnoDB tables. It's 1,000 bytes long for MyISAM tables. This limit has been increased to 3072 bytes In MySQL version 5.7 (and upwards).
You also have to be aware that if you set an index on a big char or varchar field which is utf8mb4 encoded, you have to divide the max index prefix length of 767 bytes (or 3072 bytes) by 4 resulting in 191. This is because the maximum length of a utf8mb4 character is four bytes. For a utf8 character it would be three bytes resulting in max index prefix length of 255 (or minus null-terminator, 254 characters).
One option you have is to just place lower limit on your VARCHAR fields.
Another option (according to the response to this issue) is to get the subset of the column rather than the entire amount, i.e.:
ALTER TABLE `mytable` ADD UNIQUE ( column1(15), column2(200) );
Tweak as you need to get the key to apply, but I wonder if it would be worth it to review your data model regarding this entity to see if there's improvements possible, which would allow you to implement the intended business rules without hitting the MySQL limitation.
When you hit the limit. Set the following.
INNODB utf8 VARCHAR(255)
INNODB utf8mb4 VARCHAR(191)
If anyone is having issues with InnoDB and utf8 charset trying to put a UNIQUE index on a VARCHAR(256) field, switch it to VARCHAR(255). It seems 255 is the limitation.
MySQL assumes worst case for the number of bytes per character in the string. For the MySQL 'utf8' encoding, that's 3 bytes per character since that encoding doesn't allow characters beyond U+FFFF. For the MySQL 'utf8mb4' encoding, it's 4 bytes per character, since that's what MySQL calls actual UTF-8.
So assuming you're using 'utf8', your first column will take 60 bytes of the index, and your second another 1500.
Solution For Laravel Framework
As per Laravel 5.4.* documentation; You have to set the default string length inside the boot method of the app/Providers/AppServiceProvider.php file as follows:
use Illuminate\Support\Facades\Schema;
public function boot()
{
Schema::defaultStringLength(191);
}
Explanation of this fix, given by Laravel 5.4.* documentation:
Laravel uses the utf8mb4 character set by default, which includes support for storing "emojis" in the database. If you are running a version of MySQL older than the 5.7.7 release or MariaDB older than the 10.2.2 release, you may need to manually configure the default string length generated by migrations in order for MySQL to create indexes for them. You may configure this by calling the Schema::defaultStringLength method within your AppServiceProvider.
Alternatively, you may enable the innodb_large_prefix option for your
database. Refer to your database's documentation for instructions on
how to properly enable this option.
run this query before your query:
SET ##global.innodb_large_prefix = 1;
this will increase limit to 3072 bytes.
What character encoding are you using? Some character sets (like UTF-16, et cetera) use more than one byte per character.
Replace utf8mb4 with utf8 in your import file.
But note that utf8 charset is deprecated and it does not support all Unicode characters, e.g. emojis, so you will lose full Unicode support if you do this.
I think varchar(20) only requires 21 bytes while varchar(500) only
requires 501 bytes. So the total bytes are 522, less than 767. So why
did I get the error message?
UTF8 requires 3 bytes per character to store the string, so in your case 20 + 500 characters = 20*3+500*3 = 1560 bytes which is more than allowed 767 bytes.
The limit for UTF8 is 767/3 = 255 characters, for UTF8mb4 which uses 4 bytes per character it is 767/4 = 191 characters.
There are two solutions to this problem if you need to use longer column than the limit:
Use "cheaper" encoding (the one that requires less bytes per character)
In my case, I needed to add Unique index on column containing SEO string of article, as I use only [A-z0-9\-] characters for SEO, I used latin1_general_ci which uses only one byte per character and so column can have 767 bytes length.
Create hash from your column and use unique index only on that
The other option for me was to create another column which would store hash of SEO, this column would have UNIQUE key to ensure SEO values are unique. I would also add KEY index to original SEO column to speed up look up.
The answer about why you get error message was already answered by many users here. My answer is about how to fix and use it as it be.
Refer from this link.
Open MySQL client (or MariaDB client). It is a command line tool.
It will ask your password, enter your correct password.
Select your database by using this command use my_database_name;
Database changed
set global innodb_large_prefix=on;
Query OK, 0 rows affected (0.00 sec)
set global innodb_file_format=Barracuda;
Query OK, 0 rows affected (0.02 sec)
Go to your database on phpMyAdmin or something like that for easy management. > Select database > View table structure > Go to Operations tab. > Change ROW_FORMAT to DYNAMIC and save changes.
Go to table's structure tab > Click on Unique button.
Done. Now it should has no errors.
The problem of this fix is if you export db to another server (for example from localhost to real host) and you cannot use MySQL command line in that server. You cannot make it work there.
Specified key was too long; max key length is 767 bytes
You got that message because 1 byte equals 1 character only if you use the latin-1 character set. If you use utf8, each character will be considered 3 bytes when defining your key column. If you use utf8mb4, each character will be considered to be 4 bytes when defining your key column. Thus, you need to multiply your key field's character limit by, 1, 3, or 4 (in my example) to determine the number of bytes the key field is trying to allow. If you are using uft8mb4, you can only define 191 characters for a native, InnoDB, primary key field. Just don't breach 767 bytes.
5 workarounds:
The limit was raised in 5.7.7 (MariaDB 10.2.2?). And it can be increased with some work in 5.6 (10.1).
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 values 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.
⚈ Or... Stay with older version 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)
-- http://mysql.rjweb.org/doc.php/limits#767_limit_in_innodb_indexes
you could add a column of the md5 of long columns
For laravel 5.7 to 9.0
Steps to followed
Go to App\Providers\AppServiceProvider.php.
Add this to provider use Illuminate\Support\Facades\Schema; in top.
Inside the Boot function Add this Schema::defaultStringLength(191);
that all, Enjoy.
We encountered this issue when trying to add a UNIQUE index to a VARCHAR(255) field using utf8mb4. While the problem is outlined well here already, I wanted to add some practical advice for how we figured this out and solved it.
When using utf8mb4, characters count as 4 bytes, whereas under utf8, they could as 3 bytes. InnoDB databases have a limit that indexes can only contain 767 bytes. So when using utf8, you can store 255 characters (767/3 = 255), but using utf8mb4, you can only store 191 characters (767/4 = 191).
You're absolutely able to add regular indexes for VARCHAR(255) fields using utf8mb4, but what happens is the index size is truncated at 191 characters automatically - like unique_key here:
This is fine, because regular indexes are just used to help MySQL search through your data more quickly. The whole field doesn't need to be indexed.
So, why does MySQL truncate the index automatically for regular indexes, but throw an explicit error when trying to do it for unique indexes? Well, for MySQL to be able to figure out if the value being inserted or updated already exists, it needs to actually index the whole value and not just part of it.
At the end of the day, if you want to have a unique index on a field, the entire contents of the field must fit into the index. For utf8mb4, this means reducing your VARCHAR field lengths to 191 characters or less. If you don't need utf8mb4 for that table or field, you can drop it back to utf8 and be able to keep your 255 length fields.
I fixed this issue with :
varchar(200)
replaced with
varchar(191)
all the unique or primary varchar keys which have more than 200 replace them with 191 or set them as text.
Here is my original answer:
I just drop database and recreate like this, and the error is gone:
drop database if exists rhodes; create database rhodes default
CHARACTER set utf8 default COLLATE utf8_general_ci;
However, it doesn't work for all the cases.
It is actually a problem of using indexes on VARCHAR columns with the character set utf8 (or utf8mb4), with VARCHAR columns that have more than a certain length of characters. In the case of utf8mb4, that certain length is 191.
Please refer to the Long Index section in this article for more information how to use long indexes in MySQL database: http://hanoian.com/content/index.php/24-automate-the-converting-a-mysql-database-character-set-to-utf8mb4
To fix that, this works for me like a charm.
ALTER DATABASE dbname CHARACTER SET utf8 COLLATE utf8_general_ci;
I did some search on this topic finally got some custom change
For MySQL workbench 6.3.7 Version Graphical inter phase is available
Start Workbench and select the connection.
Go to management or Instance and select Options File.
If Workbench ask you permission to read configuration file and then allow it by pressing OK two times.
At center place Administrator options file window comes.
Go To InnoDB tab and check the innodb_large_prefix if it not checked in the General section.
set innodb_default_row_format option value to DYNAMIC.
For Versions below 6.3.7 direct options are not available so need to go with command prompt
Start CMD as administrator.
Go To director where mysql server is install Most of cases its at
"C:\Program Files\MySQL\MySQL Server 5.7\bin" so command is
"cd \"
"cd Program Files\MySQL\MySQL Server 5.7\bin".
Now Run command
mysql -u userName -p databasescheema
Now it asked for password of respective user.
Provide password and enter into mysql prompt.
We have to set some global settings enter the below commands one by one
set global innodb_large_prefix=on;
set global innodb_file_format=barracuda;
set global innodb_file_per_table=true;
Now at the last we have to alter the ROW_FORMAT of required table by default its COMPACT we have to set it to DYNAMIC.
use following command
alter table table_name ROW_FORMAT=DYNAMIC;
Done
change your collation. You can use utf8_general_ci that supports almost all
Just changing utf8mb4 to utf8 when creating tables solved my problem. For example: CREATE TABLE ... DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; to CREATE TABLE ... DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;.
Index Lengths & MySQL / MariaDB
Laravel uses the utf8mb4 character set by default, which includes support for storing "emojis" in the database. If you are running a version of MySQL older than the 5.7.7 release or MariaDB older than the 10.2.2 release, you may need to manually configure the default string length generated by migrations in order for MySQL to create indexes for them. You may configure this by calling the Schema::defaultStringLength method within your AppServiceProvider:
use Illuminate\Support\Facades\Schema;
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Schema::defaultStringLength(191);
}
Alternatively, you may enable the innodb_large_prefix option for your database. Refer to your database's documentation for instructions on how to properly enable this option.
Reference from blog : https://www.scratchcode.io/specified-key-too-long-error-in-laravel/
Reference from Official laravel documentation : https://laravel.com/docs/5.7/migrations
This solved my issue
ALTER DATABASE dbname CHARACTER SET utf8 COLLATE utf8_general_ci;
Based on the column given below, those 2 variable string columns are using utf8_general_ci collation (utf8 charset is implied).
In MySQL, utf8 charset uses a maximum of 3 bytes for each character. Thus, it would need to allocate 500*3=1500 bytes, which is much greater than the 767 bytes MySQL allows. That's why you are getting this 1071 error.
In other words, you need to calculate the character count based on the charset's byte representation as not every charset is a single byte representation (as you presumed.) I.E. utf8 in MySQL is uses at most 3-byte per character, 767/3≈255 characters, and for utf8mb4, an at most 4-byte representation, 767/4≈191 characters.
It's also known that MySQL
column1 varchar(20) utf8_general_ci
column2 varchar(500) utf8_general_ci
In my case, I had this problem when I was backing up a database using the linux redirection output/input characters. Therefore, I change the syntax as described below. PS: using a linux or mac terminal.
Backup (without the > redirect)
# mysqldump -u root -p databasename -r bkp.sql
Restore (without the < redirect )
# mysql -u root -p --default-character-set=utf8 databasename
mysql> SET names 'utf8'
mysql> SOURCE bkp.sql
The error "Specified key was too long; max key length is 767 bytes" simple disappeared.
I found this query useful in detecting which columns had an index violating the max length:
SELECT
c.TABLE_NAME As TableName,
c.COLUMN_NAME AS ColumnName,
c.DATA_TYPE AS DataType,
c.CHARACTER_MAXIMUM_LENGTH AS ColumnLength,
s.INDEX_NAME AS IndexName
FROM information_schema.COLUMNS AS c
INNER JOIN information_schema.statistics AS s
ON s.table_name = c.TABLE_NAME
AND s.COLUMN_NAME = c.COLUMN_NAME
WHERE c.TABLE_SCHEMA = DATABASE()
AND c.CHARACTER_MAXIMUM_LENGTH > 191
AND c.DATA_TYPE IN ('char', 'varchar', 'text')
Please check if sql_mode is like
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
if it is, change to
sql_mode=NO_ENGINE_SUBSTITUTION
OR
restart your server changing your my.cnf file (putting following)
innodb_large_prefix=on
Due to prefix limitations this error will occur. 767 bytes is the stated prefix limitation for InnoDB tables in MySQL versions before 5.7 . It's 1,000 bytes long for MyISAM tables. In MySQL version 5.7 and upwards this limit has been increased to 3072 bytes.
Running the following on the service giving you the error should resolve your issue. This has to be run in the MYSQL CLI.
SET GLOBAL innodb_file_format=Barracuda;
SET GLOBAL innodb_file_per_table=on;
SET GLOBAL innodb_large_prefix=on;
The Problem
There are max key length limits in MySQL.
InnoDB — max key length is 1,536 bytes (for 8kb page size) and 768 (for 4kb page size) (Source: Dev.MySQL.com).
MyISAM — max key length is 1,000 bytes (Source Dev.MySQL.com).
These are counted in bytes! So, a UTF-8 character may take more than one byte to be stored into the key.
Therefore, you have only two immediate solutions:
Index only the first n'th characters of the text type.
Create a FULL TEXT search — Everything will be Searchable within the Text, in a fashion similar to ElasticSearch
Indexing the First N'th Characters of a Text Type
If you are creating a table, use the following syntax to index some field's first 255 characters: KEY sometextkey (SomeText(255)). Like so:
CREATE TABLE `MyTable` (
`id` int(11) NOT NULL auto_increment,
`SomeText` TEXT NOT NULL,
PRIMARY KEY (`id`),
KEY `sometextkey` (`SomeText`(255))
);
If you already have the table, then you can add a unique key to a field with: ADD UNIQUE(ConfigValue(20));. Like so:
ALTER TABLE
MyTable
ADD UNIQUE(`ConfigValue`(20));
If the name of the field is not a reserved MySQL keyword, then the backticks (```) are not necessary around the fieldname.
Creating a FULL TEXT Search
A Full Text search will allow you to search the entirety of the value of your TEXT field. It will do whole-word matching if you use NATURAL LANGUAGE MODE, or partial word matching if you use one of the other modes. See more on the options for FullText here: Dev.MySQL.com
Create your table with the text, and add the Full text index...
ALTER TABLE
MyTable
ADD FULLTEXT INDEX
`SomeTextKey` (`SomeTextField` DESC);
Then search your table like so...
SELECT
MyTable.id, MyTable.Title,
MATCH
(MyTable.Text)
AGAINST
('foobar' IN NATURAL LANGUAGE MODE) AS score
FROM
MyTable
HAVING
score > 0
ORDER BY
score DESC;
I have changes from varchar to nvarchar, works for me.

MySQL Error #1071 - Specified key was too long; max key length is 767 bytes

When I executed the following command:
ALTER TABLE `mytable` ADD UNIQUE (
`column1` ,
`column2`
);
I got this error message:
#1071 - Specified key was too long; max key length is 767 bytes
Information about column1 and column2:
column1 varchar(20) utf8_general_ci
column2 varchar(500) utf8_general_ci
I think varchar(20) only requires 21 bytes while varchar(500) only requires 501 bytes. So the total bytes are 522, less than 767. So why did I get the error message?
#1071 - Specified key was too long; max key length is 767 bytes
767 bytes in MySQL version 5.6 (and prior versions), is the stated prefix limitation for InnoDB tables. It's 1,000 bytes long for MyISAM tables. This limit has been increased to 3072 bytes In MySQL version 5.7 (and upwards).
You also have to be aware that if you set an index on a big char or varchar field which is utf8mb4 encoded, you have to divide the max index prefix length of 767 bytes (or 3072 bytes) by 4 resulting in 191. This is because the maximum length of a utf8mb4 character is four bytes. For a utf8 character it would be three bytes resulting in max index prefix length of 255 (or minus null-terminator, 254 characters).
One option you have is to just place lower limit on your VARCHAR fields.
Another option (according to the response to this issue) is to get the subset of the column rather than the entire amount, i.e.:
ALTER TABLE `mytable` ADD UNIQUE ( column1(15), column2(200) );
Tweak as you need to get the key to apply, but I wonder if it would be worth it to review your data model regarding this entity to see if there's improvements possible, which would allow you to implement the intended business rules without hitting the MySQL limitation.
When you hit the limit. Set the following.
INNODB utf8 VARCHAR(255)
INNODB utf8mb4 VARCHAR(191)
If anyone is having issues with InnoDB and utf8 charset trying to put a UNIQUE index on a VARCHAR(256) field, switch it to VARCHAR(255). It seems 255 is the limitation.
MySQL assumes worst case for the number of bytes per character in the string. For the MySQL 'utf8' encoding, that's 3 bytes per character since that encoding doesn't allow characters beyond U+FFFF. For the MySQL 'utf8mb4' encoding, it's 4 bytes per character, since that's what MySQL calls actual UTF-8.
So assuming you're using 'utf8', your first column will take 60 bytes of the index, and your second another 1500.
Solution For Laravel Framework
As per Laravel 5.4.* documentation; You have to set the default string length inside the boot method of the app/Providers/AppServiceProvider.php file as follows:
use Illuminate\Support\Facades\Schema;
public function boot()
{
Schema::defaultStringLength(191);
}
Explanation of this fix, given by Laravel 5.4.* documentation:
Laravel uses the utf8mb4 character set by default, which includes support for storing "emojis" in the database. If you are running a version of MySQL older than the 5.7.7 release or MariaDB older than the 10.2.2 release, you may need to manually configure the default string length generated by migrations in order for MySQL to create indexes for them. You may configure this by calling the Schema::defaultStringLength method within your AppServiceProvider.
Alternatively, you may enable the innodb_large_prefix option for your
database. Refer to your database's documentation for instructions on
how to properly enable this option.
run this query before your query:
SET ##global.innodb_large_prefix = 1;
this will increase limit to 3072 bytes.
What character encoding are you using? Some character sets (like UTF-16, et cetera) use more than one byte per character.
Replace utf8mb4 with utf8 in your import file.
But note that utf8 charset is deprecated and it does not support all Unicode characters, e.g. emojis, so you will lose full Unicode support if you do this.
I think varchar(20) only requires 21 bytes while varchar(500) only
requires 501 bytes. So the total bytes are 522, less than 767. So why
did I get the error message?
UTF8 requires 3 bytes per character to store the string, so in your case 20 + 500 characters = 20*3+500*3 = 1560 bytes which is more than allowed 767 bytes.
The limit for UTF8 is 767/3 = 255 characters, for UTF8mb4 which uses 4 bytes per character it is 767/4 = 191 characters.
There are two solutions to this problem if you need to use longer column than the limit:
Use "cheaper" encoding (the one that requires less bytes per character)
In my case, I needed to add Unique index on column containing SEO string of article, as I use only [A-z0-9\-] characters for SEO, I used latin1_general_ci which uses only one byte per character and so column can have 767 bytes length.
Create hash from your column and use unique index only on that
The other option for me was to create another column which would store hash of SEO, this column would have UNIQUE key to ensure SEO values are unique. I would also add KEY index to original SEO column to speed up look up.
The answer about why you get error message was already answered by many users here. My answer is about how to fix and use it as it be.
Refer from this link.
Open MySQL client (or MariaDB client). It is a command line tool.
It will ask your password, enter your correct password.
Select your database by using this command use my_database_name;
Database changed
set global innodb_large_prefix=on;
Query OK, 0 rows affected (0.00 sec)
set global innodb_file_format=Barracuda;
Query OK, 0 rows affected (0.02 sec)
Go to your database on phpMyAdmin or something like that for easy management. > Select database > View table structure > Go to Operations tab. > Change ROW_FORMAT to DYNAMIC and save changes.
Go to table's structure tab > Click on Unique button.
Done. Now it should has no errors.
The problem of this fix is if you export db to another server (for example from localhost to real host) and you cannot use MySQL command line in that server. You cannot make it work there.
Specified key was too long; max key length is 767 bytes
You got that message because 1 byte equals 1 character only if you use the latin-1 character set. If you use utf8, each character will be considered 3 bytes when defining your key column. If you use utf8mb4, each character will be considered to be 4 bytes when defining your key column. Thus, you need to multiply your key field's character limit by, 1, 3, or 4 (in my example) to determine the number of bytes the key field is trying to allow. If you are using uft8mb4, you can only define 191 characters for a native, InnoDB, primary key field. Just don't breach 767 bytes.
5 workarounds:
The limit was raised in 5.7.7 (MariaDB 10.2.2?). And it can be increased with some work in 5.6 (10.1).
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 values 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.
⚈ Or... Stay with older version 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)
-- http://mysql.rjweb.org/doc.php/limits#767_limit_in_innodb_indexes
you could add a column of the md5 of long columns
For laravel 5.7 to 9.0
Steps to followed
Go to App\Providers\AppServiceProvider.php.
Add this to provider use Illuminate\Support\Facades\Schema; in top.
Inside the Boot function Add this Schema::defaultStringLength(191);
that all, Enjoy.
We encountered this issue when trying to add a UNIQUE index to a VARCHAR(255) field using utf8mb4. While the problem is outlined well here already, I wanted to add some practical advice for how we figured this out and solved it.
When using utf8mb4, characters count as 4 bytes, whereas under utf8, they could as 3 bytes. InnoDB databases have a limit that indexes can only contain 767 bytes. So when using utf8, you can store 255 characters (767/3 = 255), but using utf8mb4, you can only store 191 characters (767/4 = 191).
You're absolutely able to add regular indexes for VARCHAR(255) fields using utf8mb4, but what happens is the index size is truncated at 191 characters automatically - like unique_key here:
This is fine, because regular indexes are just used to help MySQL search through your data more quickly. The whole field doesn't need to be indexed.
So, why does MySQL truncate the index automatically for regular indexes, but throw an explicit error when trying to do it for unique indexes? Well, for MySQL to be able to figure out if the value being inserted or updated already exists, it needs to actually index the whole value and not just part of it.
At the end of the day, if you want to have a unique index on a field, the entire contents of the field must fit into the index. For utf8mb4, this means reducing your VARCHAR field lengths to 191 characters or less. If you don't need utf8mb4 for that table or field, you can drop it back to utf8 and be able to keep your 255 length fields.
I fixed this issue with :
varchar(200)
replaced with
varchar(191)
all the unique or primary varchar keys which have more than 200 replace them with 191 or set them as text.
Here is my original answer:
I just drop database and recreate like this, and the error is gone:
drop database if exists rhodes; create database rhodes default
CHARACTER set utf8 default COLLATE utf8_general_ci;
However, it doesn't work for all the cases.
It is actually a problem of using indexes on VARCHAR columns with the character set utf8 (or utf8mb4), with VARCHAR columns that have more than a certain length of characters. In the case of utf8mb4, that certain length is 191.
Please refer to the Long Index section in this article for more information how to use long indexes in MySQL database: http://hanoian.com/content/index.php/24-automate-the-converting-a-mysql-database-character-set-to-utf8mb4
To fix that, this works for me like a charm.
ALTER DATABASE dbname CHARACTER SET utf8 COLLATE utf8_general_ci;
I did some search on this topic finally got some custom change
For MySQL workbench 6.3.7 Version Graphical inter phase is available
Start Workbench and select the connection.
Go to management or Instance and select Options File.
If Workbench ask you permission to read configuration file and then allow it by pressing OK two times.
At center place Administrator options file window comes.
Go To InnoDB tab and check the innodb_large_prefix if it not checked in the General section.
set innodb_default_row_format option value to DYNAMIC.
For Versions below 6.3.7 direct options are not available so need to go with command prompt
Start CMD as administrator.
Go To director where mysql server is install Most of cases its at
"C:\Program Files\MySQL\MySQL Server 5.7\bin" so command is
"cd \"
"cd Program Files\MySQL\MySQL Server 5.7\bin".
Now Run command
mysql -u userName -p databasescheema
Now it asked for password of respective user.
Provide password and enter into mysql prompt.
We have to set some global settings enter the below commands one by one
set global innodb_large_prefix=on;
set global innodb_file_format=barracuda;
set global innodb_file_per_table=true;
Now at the last we have to alter the ROW_FORMAT of required table by default its COMPACT we have to set it to DYNAMIC.
use following command
alter table table_name ROW_FORMAT=DYNAMIC;
Done
change your collation. You can use utf8_general_ci that supports almost all
Just changing utf8mb4 to utf8 when creating tables solved my problem. For example: CREATE TABLE ... DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; to CREATE TABLE ... DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;.
Index Lengths & MySQL / MariaDB
Laravel uses the utf8mb4 character set by default, which includes support for storing "emojis" in the database. If you are running a version of MySQL older than the 5.7.7 release or MariaDB older than the 10.2.2 release, you may need to manually configure the default string length generated by migrations in order for MySQL to create indexes for them. You may configure this by calling the Schema::defaultStringLength method within your AppServiceProvider:
use Illuminate\Support\Facades\Schema;
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Schema::defaultStringLength(191);
}
Alternatively, you may enable the innodb_large_prefix option for your database. Refer to your database's documentation for instructions on how to properly enable this option.
Reference from blog : https://www.scratchcode.io/specified-key-too-long-error-in-laravel/
Reference from Official laravel documentation : https://laravel.com/docs/5.7/migrations
This solved my issue
ALTER DATABASE dbname CHARACTER SET utf8 COLLATE utf8_general_ci;
Based on the column given below, those 2 variable string columns are using utf8_general_ci collation (utf8 charset is implied).
In MySQL, utf8 charset uses a maximum of 3 bytes for each character. Thus, it would need to allocate 500*3=1500 bytes, which is much greater than the 767 bytes MySQL allows. That's why you are getting this 1071 error.
In other words, you need to calculate the character count based on the charset's byte representation as not every charset is a single byte representation (as you presumed.) I.E. utf8 in MySQL is uses at most 3-byte per character, 767/3≈255 characters, and for utf8mb4, an at most 4-byte representation, 767/4≈191 characters.
It's also known that MySQL
column1 varchar(20) utf8_general_ci
column2 varchar(500) utf8_general_ci
In my case, I had this problem when I was backing up a database using the linux redirection output/input characters. Therefore, I change the syntax as described below. PS: using a linux or mac terminal.
Backup (without the > redirect)
# mysqldump -u root -p databasename -r bkp.sql
Restore (without the < redirect )
# mysql -u root -p --default-character-set=utf8 databasename
mysql> SET names 'utf8'
mysql> SOURCE bkp.sql
The error "Specified key was too long; max key length is 767 bytes" simple disappeared.
I found this query useful in detecting which columns had an index violating the max length:
SELECT
c.TABLE_NAME As TableName,
c.COLUMN_NAME AS ColumnName,
c.DATA_TYPE AS DataType,
c.CHARACTER_MAXIMUM_LENGTH AS ColumnLength,
s.INDEX_NAME AS IndexName
FROM information_schema.COLUMNS AS c
INNER JOIN information_schema.statistics AS s
ON s.table_name = c.TABLE_NAME
AND s.COLUMN_NAME = c.COLUMN_NAME
WHERE c.TABLE_SCHEMA = DATABASE()
AND c.CHARACTER_MAXIMUM_LENGTH > 191
AND c.DATA_TYPE IN ('char', 'varchar', 'text')
Please check if sql_mode is like
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
if it is, change to
sql_mode=NO_ENGINE_SUBSTITUTION
OR
restart your server changing your my.cnf file (putting following)
innodb_large_prefix=on
Due to prefix limitations this error will occur. 767 bytes is the stated prefix limitation for InnoDB tables in MySQL versions before 5.7 . It's 1,000 bytes long for MyISAM tables. In MySQL version 5.7 and upwards this limit has been increased to 3072 bytes.
Running the following on the service giving you the error should resolve your issue. This has to be run in the MYSQL CLI.
SET GLOBAL innodb_file_format=Barracuda;
SET GLOBAL innodb_file_per_table=on;
SET GLOBAL innodb_large_prefix=on;
The Problem
There are max key length limits in MySQL.
InnoDB — max key length is 1,536 bytes (for 8kb page size) and 768 (for 4kb page size) (Source: Dev.MySQL.com).
MyISAM — max key length is 1,000 bytes (Source Dev.MySQL.com).
These are counted in bytes! So, a UTF-8 character may take more than one byte to be stored into the key.
Therefore, you have only two immediate solutions:
Index only the first n'th characters of the text type.
Create a FULL TEXT search — Everything will be Searchable within the Text, in a fashion similar to ElasticSearch
Indexing the First N'th Characters of a Text Type
If you are creating a table, use the following syntax to index some field's first 255 characters: KEY sometextkey (SomeText(255)). Like so:
CREATE TABLE `MyTable` (
`id` int(11) NOT NULL auto_increment,
`SomeText` TEXT NOT NULL,
PRIMARY KEY (`id`),
KEY `sometextkey` (`SomeText`(255))
);
If you already have the table, then you can add a unique key to a field with: ADD UNIQUE(ConfigValue(20));. Like so:
ALTER TABLE
MyTable
ADD UNIQUE(`ConfigValue`(20));
If the name of the field is not a reserved MySQL keyword, then the backticks (```) are not necessary around the fieldname.
Creating a FULL TEXT Search
A Full Text search will allow you to search the entirety of the value of your TEXT field. It will do whole-word matching if you use NATURAL LANGUAGE MODE, or partial word matching if you use one of the other modes. See more on the options for FullText here: Dev.MySQL.com
Create your table with the text, and add the Full text index...
ALTER TABLE
MyTable
ADD FULLTEXT INDEX
`SomeTextKey` (`SomeTextField` DESC);
Then search your table like so...
SELECT
MyTable.id, MyTable.Title,
MATCH
(MyTable.Text)
AGAINST
('foobar' IN NATURAL LANGUAGE MODE) AS score
FROM
MyTable
HAVING
score > 0
ORDER BY
score DESC;
I have changes from varchar to nvarchar, works for me.