MySQL 长度较大的VARCHAR索引限制说明

我来为您详细说明 MySQL 中长度较大的 VARCHAR 字段的索引限制问题。

MySQL VARCHAR 索引限制说明

1. 核心限制规则

MySQL 对索引长度有以下主要限制:

InnoDB 存储引擎

  • 最大索引长度: 767 字节 (MySQL 5.6 及更早版本)
  • 最大索引长度: 3072 字节 (MySQL 5.7+, 需启用 innodb_large_prefix)
  • 行格式要求: 必须使用 DYNAMICCOMPRESSED 行格式

MyISAM 存储引擎

  • 最大索引长度: 1000 字节

2. 字符集对索引长度的影响

不同字符集下,VARCHAR 字段能创建的最大索引长度不同:

单字节字符集 (latin1):
  - 767 字节限制:VARCHAR(767)
  - 3072 字节限制:VARCHAR(3072)

多字节字符集 (utf8mb4):
  - 每个字符最多 4 字节
  - 767 字节限制:VARCHAR(191)  [767 ÷ 4 = 191.75]
  - 3072 字节限制:VARCHAR(768)  [3072 ÷ 4 = 768]

3. 实际应用场景

问题场景

-- ❌ 在 utf8mb4 字符集下会报错
CREATE TABLE users (
    name VARCHAR(500),
    INDEX idx_name (name)  -- 错误:索引长度 2000 字节 > 767 字节
);

-- ✅ 安全的做法
CREATE TABLE users (
    name VARCHAR(191),
    INDEX idx_name (name)  -- 正确:191 × 4 = 764 字节 < 767 字节
);

解决方案

方案 1: 使用前缀索引

-- 只对字段的前 N 个字符创建索引
CREATE TABLE articles (
    title VARCHAR(500),
    INDEX idx_title_prefix (title(191))  -- 只索引前 191 个字符
);

方案 2: 升级配置 (MySQL 5.7+)

-- 1. 设置 innodb_large_prefix
SET GLOBAL innodb_large_prefix = ON;

-- 2. 使用 DYNAMIC 行格式
CREATE TABLE users (
    name VARCHAR(500),
    INDEX idx_name (name)
) ROW_FORMAT=DYNAMIC;

-- 3. 或者修改现有表
ALTER TABLE users ROW_FORMAT=DYNAMIC;

方案 3: 使用全文索引 (适合长文本搜索)

CREATE TABLE articles (
    content TEXT,
    FULLTEXT INDEX ft_content (content)
);

4. 查看当前配置

-- 查看 innodb_large_prefix 设置
SHOW VARIABLES LIKE 'innodb_large_prefix';

-- 查看表的行格式
SHOW TABLE STATUS LIKE 'your_table_name';

-- 查看数据库字符集
SHOW VARIABLES LIKE 'character_set_database';

-- 查看索引长度限制
SHOW VARIABLES LIKE 'innodb_default_row_format';

5. 最佳实践建议

  1. 推荐长度: 在 utf8mb4 字符集下,VARCHAR 索引字段最好不超过 191 个字符
  2. 前缀索引: 对于较长的字段,使用前缀索引可以平衡性能和空间
  3. 避免过度索引: 过长的索引会占用更多存储空间,降低写入性能
  4. 考虑替代方案:
    • 使用哈希索引 (适合精确匹配)
    • 使用全文索引 (适合模糊搜索)
    • 使用外部搜索引擎 (如 Elasticsearch)

6. 常见错误示例

Error 1071: Specified key was too long; max key length is 767 bytes
Error 1170: BLOB/TEXT column 'xxx' used in key specification without a key length

如果您需要针对具体的业务场景选择合适的索引策略,请告诉我您的具体需求(如字段用途、查询模式等),我可以给出更精准的建议。

mysql 表分区的不同是否会影响正常使用?

MySQL 表分区的不同通常不会影响正常使用,但有一些需要注意的地方:

对正常使用的影响

基本不影响的操作

  • CRUD 操作:增删改查操作完全透明,应用层无需感知分区存在
  • 索引使用:分区表可以正常创建和使用索引
  • 事务支持:分区表完全支持事务
  • 外键约束:支持(但有局限性)

⚠️ 可能影响的情况

  1. 查询性能

    • ✅ 如果查询条件包含分区键,会进行分区裁剪,只扫描相关分区,性能更好
    • ⚠️ 如果查询不包含分区键,会扫描所有分区,可能比非分区表更慢
  2. 唯一键限制

    • ⚠️ 分区表中,主键或唯一键必须包含分区键字段
    • 这是 MySQL 的强制要求
  3. 外键约束

    • ⚠️ 分区表不支持作为子表被外键引用
    • ⚠️ 分区表也不能引用其他表的外键
  4. 维护操作

    • 某些 DDL 操作可能需要更长时间
    • 备份恢复策略可能需要调整

建议

如果你考虑使用表分区,需要确保:

  • 合理选择分区键(通常是时间、ID 等)
  • 查询条件尽量包含分区键
  • 主键设计要包含分区键

MySQL导入远程数据库

mysqldump -h 123.3.3.3 --compress --single-transaction --quick -u OOOK -pPP01 OOOK | mysql -h 127.0.0.3 -u OOOK -pPP01 OOOK
127.0.0.3 适用于连接 OOOK@%
--quick 用于省内存
--compress 用于压缩传输
--single-transaction 防止锁库

为什么basename丢失中文?

# 输入:
var_dump(basename("xf/中文qq.zip"));
# 输出:
string(12) "qq.zip"
# 期待的输出为:
string(12) "中文qq.zip"
可以发现中文部分丢失,使用下面的方法解决,或者手动拆分字符串
解决办法:
setlocale(LC_ALL, 'zh_CN.UTF-8');
var_dump(basename("xf/中文qq.zip"));

php swoole 队列、异步、协程并发curl请求

  public function curls()
    {
        cli_set_process_title(__FILE__ . ':curls');
        $file_mtime = $this->file_mtime();
        $st = time();

        error_reporting(E_ALL);
        ini_set('swoole.display_errors', 'On');

        \Swoole\Coroutine::set(['hook_flags' => SWOOLE_HOOK_ALL]);
        \Swoole\Coroutine\run(function () use ($st, $file_mtime) {
            $channel = new \Swoole\Coroutine\Channel(1);
            $exit = null;
            \Swoole\Coroutine::create(function () use ($channel, $st, $file_mtime, &$exit) {
                $redis = \services\iRedis::getInstance();
                while (true) {
                    $task = $redis->blPop('curl_queue', 5);
                    if (!empty($task)) $channel->push($task[1]);
                    if ($file_mtime != $this->file_mtime()) break;
                    if (time() - $st > 3600) break;
                }
                $exit = true;
            });
            for ($i = 0; $i < 5; $i++) {
                \Swoole\Coroutine::create(function () use ($channel, &$exit) {
                    while (true) {
                        $task = $channel->pop(5);
                        if (!empty($task)) {
                            $json = json_decode($task, true);
                            if ($json) {
                                $method = $json['method'];
                                $uri = $json['uri'];
                                $headers = $json['headers'] ?? [];
                                $body = $json['body'] ?? null;
                                $result = \services\Tools::curl($method, $uri, $body, $headers);
                            }
                        }
                        if ($exit && $channel->isEmpty()) break;
                    }
                });
            }
        });
    }

微信PHP解密

openssl 实现:

        $result = openssl_decrypt(base64_decode($data),
            "AES-128-CBC",
            base64_decode($key),
            OPENSSL_RAW_DATA,
            base64_decode($iv));
        var_dump($result);

mcrypt 实现


        $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
        //用密钥key、初始化向量初始化
        mcrypt_generic_init($module, base64_decode($key), base64_decode($iv));
        //**执行解密**(得到带有PKCS#7填充的半原文,所以要去除填充)
        $result = mdecrypt_generic($module, base64_decode($data));
        //清理工作与关闭解密
        mcrypt_generic_deinit($module);
        mcrypt_module_close($module);
        //去除填充
        $lastByte = substr($result, -1);
        $result = substr($result, 0, strlen($result) - ord($lastByte));
        var_dump($result);

PHP单机文件排队锁


class Locker
{

    private static array $lockers = [];

    /**
     * 获取锁
     * @param string $key 锁的唯一标识
     * @param int $timeout 超时时间(秒),0表示无限等待
     * @return bool 是否获取到锁
     */
    public static function wait(string $key, int $timeout = 0): bool
    {
        $file = sys_get_temp_dir() . "/.lock_" . md5($key) . ".tmp";
        $start_time = time();

        while (true) {
            // 检查是否超时
            if ($timeout > 0 && (time() - $start_time) >= $timeout) {
                return false;
            }

            // 尝试打开文件
            $fp = @fopen($file, "c+");
            if (!$fp) {
                usleep(10000); // 等待10毫秒
                continue;
            }

            // 尝试获取锁
            if (flock($fp, LOCK_EX | LOCK_NB)) {
                // 获取锁成功
                self::$lockers[$key] = $fp;
                return true;
            } else {
                // 获取锁失败,关闭文件句柄
                fclose($fp);
                usleep(10000); // 等待10毫秒后重试
            }
        }
    }

    /**
     * 释放锁
     * @param string $key 锁的唯一标识
     * @return bool 是否成功释放
     */
    public static function release(string $key): bool
    {
        if (!isset(self::$lockers[$key])) {
            return false;
        }

        $fp = self::$lockers[$key];
        if (is_resource($fp)) {
            flock($fp, LOCK_UN); // 释放文件锁
            fclose($fp);         // 关闭文件句柄
        }

        unset(self::$lockers[$key]);
        return true;
    }

    /**
     * 检查是否持有某个锁
     * @param string $key 锁的唯一标识
     * @return bool 是否持有锁
     */
    public static function isLocked(string $key): bool
    {
        return isset(self::$lockers[$key]) && is_resource(self::$lockers[$key]);
    }

    /**
     * 析构时释放所有锁
     */
    public function __destruct()
    {
        foreach (array_keys(self::$lockers) as $key) {
            self::release($key);
        }
    }
}

使用:
        if (!Locker::wait(md5("key"), 3)) {
            recordlog("3秒内获取不到锁返回提示 请勿重复请求  ");
            # 3秒内获取不到锁返回提示
            exit_json(0, '请勿重复请求');
        }

MySQL 里 varchar(64) 采用utf-8编码,能存多少个汉字?或多少个字母?

MySQL 中 varchar(64) 采用 UTF-8 编码时的存储能力可通过以下维度分析:


一、存储汉字

  1. 字符数量
    varchar(64) 可直接存储 64 个汉字,因为定义长度单位为字符而非字节。
    例如:varchar(64) 字段可完整存入 “数据库设计规范与最佳实践”(假设总长度不超过 64 个汉字)。
  2. 字节占用
    每个 UTF-8 汉字占 3 字节,64 个汉字共占 64 × 3 = 192 字节
    (注:实际存储时会额外消耗 1-2 字节记录字段长度,但因 64 < 255,仅需 1 字节长度前缀)

二、存储字母/数字

  1. 字符数量
    可存储 64 个字母或数字,同样以字符为单位计算。
    例如:varchar(64) 可存入 “ABCDabcd1234…” 共 64 个字符。
  2. 字节占用
    每个英文字符占 1 字节,总字节数为 64 × 1 = 64 字节

三、关键限制

  1. 总行长度限制
    MySQL 单行所有字段的字节总和不得超过 65535 字节(含长度前缀和字段元数据)。
  • 示例:若 varchar(64) 存储 64 个汉字,总字节数为 192 + 1 = 193 字节,远低于行限制。
  1. 编码版本差异
  • MySQL 5.0 以上varchar(n) 以字符为单位,兼容 UTF-8 汉字和字母混合存储。
  • MySQL 4.0 以下varchar(n) 以字节为单位(需避免使用旧版本)。

四、实际场景建议

  1. 混合存储
    若字段包含汉字和字母混合内容,总字符数不超过 64 即可,MySQL 会自动处理字节转换。
    例如:”用户ID_12345″(10 字符)占用 10 × 3 = 30 字节(汉字部分) + ASCII 字符的 1 字节/字符。
  2. 字符集升级
    推荐使用 utf8mb4 替代 utf8,以支持更多 Unicode 字符(如 emoji),但需注意存储空间需求增加。

总结

  • 汉字容量:64 个
  • 字母/数字容量:64 个
  • 实际存储:无需人工截断,MySQL 按字符计数自动管理。
  • 风险提示:需确保总行长度不超过 65535 字节,避免设计多超长字段的表。

引用链接:
1.MySQL:一场由Char/Varchar 引起的战争!! – 知乎
2.mysql数据库:varchar类型可以存储多少个汉字,多少个数字 – 腾讯云
3.MySQL utf8编码的varchar最多能存多少个字符 – CSDN博客
4.mysql utf-8 中文 – CSDN博客
5.mysql的varcher类型长度 – 根号三
6.MySQL 数据库 varchar 到底可以存多少个汉字,多少个英文呢?我们来搞搞清楚 · Ruby China – ruby-china.org
7.数据库中varchar类型 最大长度是多少?[通俗易懂] – 腾讯云
8.mysql 64字节能存多少个汉字 – 51CTO博客
9.MySQL中varchar能存多少汉字、数字 – 阿里云开发者社区
10.mysql varchar可以存几个汉字 – CSDN博客
11.mysql varchar到底能存多少汉字? – CSDN博客
12.MySQL数据类型 – 程序员肥仔
13.mysql varchar 100 可以存多少汉字 utf8编码 – 51CTO博客
14.软件编程基础知识:mysql不同字段类型分别可以存储多少内容? – 学科学玩数码
15.MYSQL_第11章_MySQL数据类型详解 – YOLO
16.mysql一个汉字几个字符 mysql里汉字占几位 – 51CTO博客
17.mysql的varchar到底能存多少个字符 – 腾讯云
18.mysql数据类型char与varchar的区别 – 博客园
19.关于MySQL VARCHAR的错误经验,你中了几条? – 稀土掘金