admin

  • 存储引擎在存储整数时,一般会使用最高位作为标志位,标记存储的整数是正数还是负数(参考),最高位也被称为“most significant bit (MSb)”。通常,最高位为1则表示正数,最高位为0,则表示负数。更进一步的,负数则会通过补码(参考:two’s complement)的方式表示。但是,InnoDB没有使用这种方法。

    InnoDB 的整数存储

    在死锁诊断时,偶然注意到,InnoDB 在存储整数时,与一般的系统是不同的。例如,int 类型存储 1 的时候,使用的表示是:0x80000001。更多的示例可以参考右图:

    整数值InnoDB 表示
    10x80000001
    -10x7fffffff
    70x80000007
    -70x7ffffff9

    可以看到,这与一般的有符号型的整数存储是相反的。即:

    • 正数表示时,最高位(MSb)为1
    • 负数表示时,最高位(MSb)为0

    关于这个问题,在 Stackoverflow上也有看到有部分用户有类似的疑问:

    本文将讨论为什么会这样。

    考虑 8-bit 场景下的

    这里来回顾一下“体系结构”中的最为基础的一些知识吧。

    整数值绝对值绝对值的二进制原码2-补码Offset binary(“移码”)
    110000-00010000-00010000-00011000-0001
    -110000-00011000-00011111-11110111-1111
    770000-01110000-01110000-01111000-0111
    -770000-01111000-01111111-100101111001

    说明:

    移码有两种计算方式,结果是等价的,即:

    • 直接将原始数据加上2(n-1),然后转化为二进制即可以
    • 将其补码,最高位进行一次翻转,即 “补码 XOR 2(n-1)

    验证存储方式

    为了确认 InnoDB 的整数处理,再MySQL 8.4.4的源码中找到如下 InnoDB 处理整型数据的代码:

      if (type == DATA_INT) {
        /* Store integer data in Innobase in a big-endian format,
        sign bit negated if the data is a signed integer. In MySQL,
        integers are stored in a little-endian format. */
    
        byte *p = buf + col_len;
    
        for (;;) {
          p--;
          *p = *mysql_data;
          if (p == buf) {
            break;
          }
          mysql_data++;
        }
    
        if (!(dtype->prtype & DATA_UNSIGNED)) {
          *buf ^= 128;
        }
    
        ptr = buf;
        buf += col_len;

    这段代码中,先将字节序做了颠倒(从最高字节位开始,逐个字节进行拷贝存储),即将 MySQL 层面的小端(little-endian)转化为了InnoDB层面的(big-endian)存储。而后,再对最高位进行了一次翻转,即这里的:*buf ^= 128操作。

    即:先将数据在MySQL层面的表示做了大小端的转化并拷贝过来,然后,将最高位进行翻转。即,先将2补码的表示模式拷贝过来,再将最高位进行翻转。

    什么要这么存储

    在 MySQL/InnoDB 官方文档或者代码中,并没有关于该实现的说明。不过这么做,有一个非常明显的好处,即所有的整数表示的大小关系,与实际存储的数据(当中无符号型对待)的大小关系是一致的。

    即,在上述的例子中:7 > 1 > -1 > -7,而对应的编码表示,也有对应的大小关系:

    0x80000007 > 0x80000001 >0x7fffffff > 0x7ffffff9

    这里对这个问题做一个简单探讨。先说结论吧,这是一种较为典型的整数编码方式:Offset binary(“移码”)。即,将需要表示的整数,加上一个数值,让所有的整数映射到自然数空间。例如,在MySQL中使用32位的int类型,需要表示的整数范围为[-231,231]。那么,实际表示时,则加上231。更为一般的,对于[-2(n-1), 2(n-1)]之间的所有整数在表示时,都加上了2(n-1)。即,建立的映射关系是:

    f(i) = i + 2(n-1)

    即对于任何要存储的整数i,实际存储时都存储上述的f(i)。而在实际运算时,则是,将补码的最高位进行一次翻转即可。

    关于补码

    例如,在 8 位二进制中,00000001 表示 +1,而 11111111 代表 -1。具体的,在表示-3 时,先取 3 的二进制 00000011,再逐位取反 11111100,最后加 1 得到 11111101,即 -3 的补码表示。这种方式让计算机能够高效地进行整数运算,是典型的正负数的方法,该方法的更多优势可以参考:two’s complement

    补充说明

    MySQL 层面的整数表示和 InnoDB 的整数存储是不同的。在“验证存储方式”小结中的代码中可以看到:

    • MySQL使用了小端(little-endian),InnoDB层面使用了大端(big-endian)存储
    • 在 MySQL 层面使用2-补码做有符号整数类型存储;而InnoDB层面使用了“移码”存储

    参考文档

  • InnoDB 的锁诊断是一个比较困难的事情。首先,锁机制是一种较为复杂的资源竞争管理机制,如果涉及的资源类型比较多,锁类型又比较多,那么锁机制就会看起来比较复杂。具体到,MySQL/InnoDB 上,确实也就非常复杂了。这里主要关注 InnoDB 层面的锁,涉及的内容则包括了记录锁(record)、间隙锁(gap)、索引缝隙锁(next-key),而为什么要加锁,则又涉及到隔离级别、MVCC的实现,而锁的实现,则又与 InnoDB 底层的数据存储结构有有一定的关系,总得来说涉及的面比较多,如果对于这些概念没有了解,则比较难理解 InnoDB 的锁机制,也就比较难去排查 InnoDB 锁出现的问题。

    另一个层面是排查手段。MySQL/InnoDB在早期的版本中,对于锁问题的排查手段是比较有限的,而且与很多的配置参数有关,所以了解这些参数,熟悉MySQL/InnoDB锁信息查看的一些方法,则是另一个需要了解的。

    所以,关于 InnoDB 锁问题的也并不是一两个话题能够说清楚的。本文可能是一个系列(给自己挖坑),一个自己学习以及锁问题排查经验的分享。

    构造主键锁竞争

    记录锁,应该是 InnoDB 锁类型中较为常见,也是在READ-COMMITTED事务级别下,比较容易遇到的死锁类型(如果有死锁的话)。这里通过观察主键死锁、唯一键死锁,初步了解 InnoDB 锁信息内容,以及结构。

    查看当前的隔离级别

    mysql> show global variables like '%iso%';
    +-----------------------+----------------+
    | Variable_name         | Value          |
    +-----------------------+----------------+
    | transaction_isolation | READ-COMMITTED |
    +-----------------------+----------------+
    1 row in set (0.01 sec)
    
    mysql> show session variables like '%iso%';
    +-----------------------+----------------+
    | Variable_name         | Value          |
    +-----------------------+----------------+
    | transaction_isolation | READ-COMMITTED |
    +-----------------------+----------------+
    1 row in set (0.00 sec)

    准备表结构

    DROP TABLE IF EXISTS t1;
    
    CREATE TABLE t1 ( 
      id int unsigned, 
      nick varchar(32),
      age int,
      primary key (id)
    )
    mysql> desc t1;
    +-------+--------------+------+-----+---------+-------+
    | Field | Type         | Null | Key | Default | Extra |
    +-------+--------------+------+-----+---------+-------+
    | id    | int unsigned | NO   | PRI | NULL    |       |
    | nick  | varchar(32)  | YES  |     | NULL    |       |
    | age   | int          | YES  |     | NULL    |       |
    +-------+--------------+------+-----+---------+-------+

    构建锁等待

    在两个会话中,按从上到下,执行下述的SQL:

    时间顺序Session ASession B
    1START TRANSACTION;
    2INSERT INTO t1 VALUES ( 1, "a",12 );
    3START TRANSACTION;
    4INSERT INTO t1 VALUES ( 1, "x",23 );

    这时候,Session B会陷入等待。

    观察锁信息

    通过 SHOW INNODB STATUS 观察

    此时查看 InnoDB 锁信息,则有如下数据:

    ---TRANSACTION 10094, ACTIVE 14 sec inserting
    mysql tables in use 1, locked 1
    LOCK WAIT 2 lock struct(s), heap size 1128, 1 row lock(s)
    MySQL thread id 84, OS thread handle 140453961758272, query id 9262 10.88.0.1 sysb update
    INSERT INTO t1 VALUES ( 1, "x",23 )
    ------- TRX HAS BEEN WAITING 14 SEC FOR THIS LOCK TO BE GRANTED:
    RECORD LOCKS space id 27 page no 4 n bits 72 index PRIMARY of table `sysbenchdb`.`t1` trx id 10094 lock mode S locks rec but not gap waiting
    Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
     0: len 4; hex 00000001; asc     ;;
     1: len 6; hex 00000000276d; asc     'm;;
     2: len 7; hex 810000008d0110; asc        ;;
     3: len 1; hex 61; asc a;;
     4: len 4; hex 8000000c; asc     ;;
    
    ------------------
    ---TRANSACTION 10093, ACTIVE 23 sec
    2 lock struct(s), heap size 1128, 1 row lock(s), undo log entries 1
    MySQL thread id 83, OS thread handle 140454159017536, query id 9260 10.88.0.1 sysb

    SHOW ENGINE INNODB STATUS\G中仅打印了处于等待授予状态的锁信息,即这里仅打印了事务10094的等待的锁详情。

    详解锁信息

    这里详细看看其中的内容:

    RECORD LOCKS space id 27 page no 4 n bits 72 index PRIMARY of table `sysbenchdb`.`t1` trx id 10094 lock mode S locks rec but not gap waiting
    “RECORD LOCKS”这是一个记录锁
    space id 27 page no 4该记录处于物理位置,包括页面编号,以及页面所处的物理文件编号
    n bits 72该页面有72个记录对应标记位
    index PRIMARY of table sysbenchdb.t1 对应表
    trx id 10094所在的事务 ID
    lock mode S锁类型,为 S ,即共享锁
    locks rec but not gap这是一个简单记录锁,无需对记录前的“间隙”加锁
    Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
     0: len 4; hex 00000001; asc     ;;
     1: len 6; hex 00000000276d; asc     'm;;
     2: len 7; hex 810000008d0110; asc        ;;
     3: len 1; hex 61; asc a;;
     4: len 4; hex 8000000c; asc     ;;
    Record lock(等待的)记录锁
    heap no 2在页面中,记录以堆的方式存放,该记录的堆编号
    PHYSICAL RECORD: n_fields 5;记录共五个字段
    0: len 4; hex 00000001; asc ;;id 字段值,为1
    1: len 6; hex 00000000276d; asc 'm;;该记录的DB_TRX_ID,即为0x276d10093
    2: len 7; hex 810000008d0110; asc ;;DB_ROLL_PTR
    3: len 1; hex 61; asc a;;字段nick取值 a
    4: len 4; hex 8000000c; asc ;;字段age取值 0x8000000c,即12

    这里需要注意的是,这里一共有五个字段(n_fields 5; )。那实际这个表,只有三个字段,为什么这里会有五个字段?原因在于,InnoDB 在存储数据信息的时候,会额外的存储两个信息:DB_TRX_IDDB_ROLL_PTR。这是两个InnoDB的较为底层的概念,具体的可以参考该文档:17.3 InnoDB Multi-Versioning

    注意到,这里的 DB_TRX_ID 取值为 0x276d,转化为10进制则为:10093。即,该条记录最后一次被修改是被事务10093所修改,即上述表格中的Session A所执行的SQL所修改。

    在内置视图中查看锁信息

    mysql> SELECT
        ->     ENGINE_TRANSACTION_ID AS TRX_ID,
        ->     OBJECT_NAME,
        ->     INDEX_NAME,
        ->     LOCK_TYPE,
        ->     LOCK_MODE,
        ->     LOCK_STATUS,
        ->     LOCK_DATA
        ->   FROM performance_schema.data_locks;
    +--------+-------------+------------+-----------+---------------+-------------+-----------+
    | TRX_ID | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
    +--------+-------------+------------+-----------+---------------+-------------+-----------+
    |  10224 | t1          | NULL       | TABLE     | IX            | GRANTED     | NULL      |
    |  10225 | t1          | NULL       | TABLE     | IX            | GRANTED     | NULL      |
    |  10224 | t1          | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 1         |
    |  10225 | t1          | PRIMARY    | RECORD    | S,REC_NOT_GAP | WAITING     | 1         |
    +--------+-------------+------------+-----------+---------------+-------------+-----------+

    (注:这里的TRX_ID与上述不同,因为这是重新执行了上述的冲突事务)

    补充说明

    在本示例中,写入的数据仅为主键(也是一种唯一键),故无论是READ-COMMITTED、还是REPEATABLE-READ,其所需要的锁都相同的。

    相关参考资源

    1. InnoDB Locking and Transaction Model@MySQL 8.4 Reference Manual
    2. InnoDB Data Locking – Part 1 “Introduction”@MySQL Blog Archive
    3. InnoDB Data Locking – Part 2 “Locks”@MySQL Blog Archive
    4. InnoDB Data Locking – Part 2.5 “Locks” (Deeper dive)@MySQL Blog Archive
    5. InnoDB Data Locking – Part 3 “Deadlocks”@MySQL Blog Archive
    6. Understanding InnoDB Locks and Deadlocks@2015 Percona Live
    7. Introduction to Transaction Locks in InnoDB Storage Engine@2013
  • 标题: OceanBase 单机版邀测发布,资源要求2c6g;腾讯云发布双节点经济型规格; Amazon RDS支持 MySQL 9.2

    重要更新

    3月27日,OceanBase 合作伙伴大会在北京举行,单机版发布并开启邀测[1]

    更新详情

    阿里云
    • RDS SQL Server支持自定义慢日志阈值。具体的,可在参数管理页面通过设置rds_slow_log_threshold灵活调整SQL慢日志采集阈值,精准捕捉性能瓶颈相关的SQL语句,从而快速定位问题根源。[4]
    Azure(微软云)
    • Azure Database for PostgreSQL 的长期备份保留、按需备份正式 GA [5][6]
    • Azure Cosmos DB 中多区域写入帐户的时间点还原功能公测发布 [7]
    • 适用于 Azure Functions 的 Azure Database for MySQL 触发器公测发布 [8]
    GCP(谷歌云)
    火山云(字节)
    • veDB MySQL 支持参数模板的方式批量进行参数管理 [28]
    • veDB MySQL 部分参数支持与参数规格进行联动配置 [30]
    • veDB MySQL 新创建实例的默认读写终端,以及用户新建的自定义读写终端,其一致性级别的默认值将由会话一致性调整为最终一致性 [31]
    • 托管 SQL Server 支持获取连接云数据库 SQL Server 版实例的客户端 IP。[34]
    • 托管 SQL Server 物理备份方式中全量备份的基础上增加了差异备份。[37]
    • 托管 MongoDB 副本集实例,以及分片集群实例中 新增支持 oplogMinRetentionHours 参数管理 [39]
    • 托管 MongoDB 优化了 transactionLifetimeLimitSeconds 参数,将参数取值范围上限调整为 200 [40]
    AWS(亚马逊云)
    • RDS 在数据库预览环境中支持 MySQL 9.2 [42]
    • RDS for SQL Server 支持链接到 Teradata 数据库的服务器 [48]
    腾讯云
    • 云数据库 MySQL 5.7内核版本更新20250330。[58]
    • 云数据库 MySQL 全新支持双节点经济型实例。新架构提供稳定服务,满足业务所需的计算和存储需求的同时降低了使用成本,为中小型企业、个人开发者提供更加适配业务需求的数据库服务。[59]
    • 云数据库 MySQL 只读分析引擎发布了全新的问题修复版本1.2404.22.1与2.2410.4.1。[60]
    • TDSQL-C MySQL 版只读分析引擎发布了全新的问题修复版本1.2404.22.1与2.2410.4.1。[61]
    • TDSQL-C MySQL 版8.0内核版本更新3.1.15.006,提升数据库性能与稳定性。[62]

    参考链接

  • Sysbench 是 MySQL 社交常用的压测工具,本文对 Sysbench 默认压测表大小进行分析,以帮助开发者了解该测试运行时的数据量大小。

    结论概述

    测试了 100万、500万数据,占用空间大小可以参考如下数据:

    sysbench 表大小预估单表记录表数量总空间预估
    场景 11,000,000102.3 GB
    场景 25,000,0001618.4 GB
    场景 310,000,0001636.32 GB

    生成数据命令

    这里使用如下的命令进行数据生成

    sysbench --mysql-host=... --mysql-db=sysbenchdb --db-driver=mysql \
             --mysql-user=... --mysql-password=...   \
             --table_size=1000000 --tables=10 prepare

    占用空间大小

    可以通过如下命令观察数据库中的表大小:

    MySQL [sysbenchdb]> show table status like '%sbtest1%'\G
    *************************** 1. row ***************************
               Name: sbtest1
             Engine: InnoDB
            Version: 10
         Row_format: Dynamic
               Rows: 986400
     Avg_row_length: 228
        Data_length: 225132544
    Max_data_length: 0
       Index_length: 16269312
          Data_free: 4194304
     Auto_increment: 1000001
        Create_time: 2025-03-01 05:57:45
        Update_time: 2025-03-01 05:57:45
         Check_time: NULL
          Collation: utf8mb4_0900_ai_ci
           Checksum: NULL
     Create_options:
            Comment:

    可以看到,单表约为 230 MB

    (225132544+16269312)/1024/1024 ~ 230 

    再通过数据库的物理文件观察大小:

    # ls -lah
    total 2.4G
    drwxr-x---  2 mysql mysql 4.0K Mar  1 14:04 .
    drwxr-x--x 11 mysql mysql 4.0K Mar  1 14:02 ..
    -rw-r-----  1 mysql mysql 240M Mar  1 14:04 sbtest10.ibd
    -rw-r-----  1 mysql mysql 240M Mar  1 14:00 sbtest1.ibd
    -rw-r-----  1 mysql mysql 240M Mar  1 14:01 sbtest2.ibd
    -rw-r-----  1 mysql mysql 240M Mar  1 14:01 sbtest3.ibd
    -rw-r-----  1 mysql mysql 240M Mar  1 14:02 sbtest4.ibd
    -rw-r-----  1 mysql mysql 240M Mar  1 14:02 sbtest5.ibd
    -rw-r-----  1 mysql mysql 240M Mar  1 14:03 sbtest6.ibd
    -rw-r-----  1 mysql mysql 240M Mar  1 14:03 sbtest7.ibd
    -rw-r-----  1 mysql mysql 240M Mar  1 14:03 sbtest8.ibd
    -rw-r-----  1 mysql mysql 240M Mar  1 14:04 sbtest9.ibd

    可以看到,单表大小约为 240 MB,与上述计算的 230 MB 并无太大差距。

    数据生成时间统计

    这里使用的是一台 Amazon RDS xlarge 规格的实例,存储类型为io1,iops规格为3000,在该环境下,大约18秒完成一个表的初始化,算是比较快的速度,即每秒写入约为5.5万记录。

    关于“标准测试”

    在“云数据库 MySQL 的对比测试”中,选择了4c16g的规格,压测时使用了10个记录数为100万的表,即按上述计算,数据量大小约为2.3GB。即,所有的数据均可以缓存再内存当中。这也是为什么该测试是一个 CPU 密集型测试的主要原因。

    如果考虑将数据量调整为 500万,即单表约为1.15GB,总集16个表,那么数据量就可能达到 18.4 GB,那么这个测试就可能成为一个IO密集型的测试,准确的说,可能是一个读IO密集型的测试。

    sysbench 表大小预估单表记录表数量总空间预估
    场景 11,000,000102.3 GB
    场景 25,000,0001618.4 GB

    更多统计

    Sysbench 500万数据大小
    bash-5.1# ls -l
    -rw-r----- 1 mysql mysql 1220542464 Mar 23 02:18 sbtest1.ibd
    mysql> show table status\G
    *************************** 1. row ***************************
               Name: sbtest1
             Engine: InnoDB
            Version: 10
         Row_format: Dynamic
               Rows: 4938540
     Avg_row_length: 205
        Data_length: 1017118720
    Max_data_length: 0
       Index_length: 0
          Data_free: 3145728
     Auto_increment: 5000001
        Create_time: 2025-03-23 02:17:49
        Update_time: 2025-03-23 02:17:49
         Check_time: NULL
          Collation: utf8mb4_0900_ai_ci
           Checksum: NULL
     Create_options:
            Comment:
    1 row in set (0.01 sec)

    可以看到,实际占用空间:1.137GB ~ 1220542464/1024/1024。那么,依次数据,16个大小约为 18.19 GB。与上述数据并无太大差距。

    Sysbench 1千万数据大小

    如下数据统计了,1000万数据大小。可以看到,总占用磁盘空间:2.27GB = 2436890624/1024/1024/1024

    bash-5.1# ls -l
    -rw-r----- 1 mysql mysql 2436890624 Mar 23 02:42 sbtest1.ibd
    mysql> show table status\G
    *************************** 1. row ***************************
               Name: sbtest1
             Engine: InnoDB
            Version: 10
         Row_format: Dynamic
               Rows: 9868349
     Avg_row_length: 220
        Data_length: 2179989504
    Max_data_length: 0
       Index_length: 0
          Data_free: 5242880
     Auto_increment: 10000001
        Create_time: 2025-03-23 02:41:25
        Update_time: 2025-03-23 02:41:25
         Check_time: NULL
          Collation: utf8mb4_0900_ai_ci
           Checksum: NULL
     Create_options:
            Comment:
    1 row in set (0.01 sec)
    
    mysql> select count(1) from sbtest1;
    +----------+
    | count(1) |
    +----------+
    | 10000000 |
    +----------+
    1 row in set (2.74 sec)
  • 标题:ClickHouse收购开源可观察平台HyperDX;天翼云TeleDB荣登TPC-DS测评榜第二

    重要更新

    近日,ClickHouse 宣布收购开源可观察平台HyperDX,致力于将向开发者或企业提供最快、最具成本优势和可扩展的可观察性平台。[1]

    天翼云 TeleDB 荣登 TPC-DS 全球测评总榜第二(10 TB)[2],该榜单前五的数据库分别为 TDSQL、TeleDB、阿里云 AnalyticDB、阿里云 E-MapReduce、GBase(H3C提交);100 TB 榜单为:Databricks、阿里云 E-MapReduce。

    更新详情

    GCP(谷歌云)
    • AlloyDB 集群支持使用 Private Service Connect 连接 [7]
    • Gemini Cloud Assist 支持自然语言生成 SQL [11]
    火山云(字节)
    • 托管 HBase 支持设置默认白名单,新创建的实例会自动与默认白名单进行绑定。[14]
    百度云
    • 托管 Redis支持大版本升级[24]
    AWS(亚马逊云)
    • RDS for PostgreSQL、MySQL 和 MariaDB 现在更多区域支持 M8g 和 R8g [31]
    • Aurora 现在更多区域支持 M8g 和 R8g [32]
    • RDS for MySQL 宣布扩展支持次要版本 5.7.44-RDS.20250213 [39]
    腾讯云
    • 云数据库 MySQL 只读分析引擎、TDSQL-C MySQL 发布了全新的问题修复版本1.2404.22.0与2.2410.4.0 [41][43]
    • 云数据库 MySQL 、TDSQL-C MySQL发布数据库代理版本1.4.4 [42][44]

    参考链接

  • 理解 MySQL 隐式主键

    ·

    隐式主键是 MySQL 8.0 版本新增的一个重要特性。可以非常好的解决了诸如无主键大表更新时的主备延迟问题,大大提升了主备高可用架构的“可用性”。

    为什么需要隐式主键

    最早不得不引入隐式主键功能的,大概是云厂商。

    很早,在 MySQL 运维的过程中就发现了有一类复制延迟问题,非常难缠。当主库的表没有主键/唯一键时,在主库使用一条 UPDATEDELETE操作了大量记录,在使用ROW模式的备库中,则会收到对应的、大量的变更记录,而这些变更记录在备库上应用(apply)时,因为没有主键或者唯一索引,每一条变更的回放都需要很长时间,最终导致主备之间无法追上的延迟。

    所以,在很早的时候,MySQL 规范中就有一条,表必须要有主键。对于企业,也许可以通过规范,或者调整表结构去绕开这个问题,但是,对于提供数据库托管服务的云厂商来说,却没法去要求上面使用数据库的用户去做任何适配。但是,云厂商有需要为这些数据库服务提供基于主备的高可用能力。这就陷入了一个困境,这也是为什么云厂商可能是最早需要解决这个问题的。

    早在 2016 年,阿里云的 RDS 就已经通过引入隐式主键解决类似的问题:MySQL · 最佳实践 · RDS 只读实例延迟分析

    MySQL的实现方案

    相比于社区的实现,MySQL 官方的实现考虑的更加全面,首先引入不可见列、不可见索引等特性,然后再在此基础上实现隐式主键,也全面的考虑对历史版本的兼容性、对复制的影响、对备份的影响、对各类操作命令的影响等。

    在 MySQL 8.0.30 版本(2022年07月)中,官方MySQL正式引入了隐式主键的功能。对于所有没有显式主键的 InnoDB 表,都会新增一个如下的隐式主键:

    my_row_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT INVISIBLE PRIMARY KEY

    所以,甚至在你新建一个 InnoDB 表时,如果你没有显式的主键,那么字段名 my_row_id就不能再使用了。也因为该版本是通过 INVISIBLE COLUMN 实现的,所以可以通过ALTER TABLE t1 CHANGE COLUMN...命令将隐式主键转换为普通列。

    打开隐式主键功能

    • 参数 show_gipk_in_create_table_and_information_schema 则可以控制在SHOW以及 information_schema中是否展示隐式主键信息,该参数可以帮助使用SHOW以及 information_schema的应用程序,依旧保持很好的兼容性。

    其他相关的参数包括:

    • sql_require_primary_key :该参数可以强制要求数据库中的表尽量有主键。例如,创建表、ALTER表时都需要表有主键;删除表的主键失败等,总之,尽可能的要求表均有主键。
    • REQUIRE_TABLE_PRIMARY_KEY_CHECK 这是复制配置时的选项,该参数控制的是复制时的应用线程(apply)如何检查表是否有主键,该选项的取值为:{STREAM | ON | OFF | GENERATE}。该参数可以很好的控制,从主库复制过来的表,对主键配置的要求。

    DDL 、复制与Binlog

    如果MySQL开启了隐式主键,那么就像invisible column一样,CREATE TABLEALTER TABLE的创建的隐式主键也会存储在 Binlog 中,所以备库如果在复制时,也可以活动对应的信息。

    mysql> set session sql_generate_invisible_primary_key=ON;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> create table t1_no_pk(n char(10),age int);
    Query OK, 0 rows affected (0.02 sec)
    
    mysql> show create table t1_no_pk\G
    *************************** 1. row ***************************
           Table: t1_no_pk
    Create Table: CREATE TABLE `t1_no_pk` (
      `my_row_id` bigint unsigned NOT NULL AUTO_INCREMENT /*!80023 INVISIBLE */,
      `n` char(10) DEFAULT NULL,
      `age` int DEFAULT NULL,
      PRIMARY KEY (`my_row_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
    1 row in set (0.00 sec)

    再来使用mysqlbinlog命令看看对应 binlog格式:

    # at 653
    #250316 15:02:05 server id 1  end_log_pos 928 CRC32 0x9ca72462 	Query	thread_id=9	exec_time=0	error_code=0	Xid = 31
    SET TIMESTAMP=1742108525/*!*/;
    /*!80013 SET @@session.sql_require_primary_key=0*//*!*/;
    CREATE TABLE `t1_no_pk` (
      `my_row_id` bigint unsigned NOT NULL AUTO_INCREMENT /*!80023 INVISIBLE */,
      `n` char(10) DEFAULT NULL,
      `age` int DEFAULT NULL,
      PRIMARY KEY (`my_row_id`)
    )
    /*!*/;
    SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
    DELIMITER ;
    # End of log file
    /*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
    /*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

    参考阅读