MySQL中rowid的概念你可能听说过,但是很难测试和实践,难免会有有一些疑惑,比如:1)如何感受rowid的存在2)rowid和主键有什么关系3)主键的使用存在哪些隐患4)如何理解rowid的潜在瓶颈和调试和验证?,测试环境基于MySQL5.7.19版本。问题一:如何感受rowid的存在,我们不妨用一个案例来说明。记得有一天在统计备份数据的时候,写了一条SQL语句。看到执行结果,发现SQL语句没有写完整。做完统计工作,就要去分析SQL语句了。mysql>selectbackup_date,count(*)piece_nofromredis_backup_result;+------------+----------+|backup_date|piece_no|+--------------+--------+|2018-08-14|40906|+------------+----------+1rowinset(0.03sec)根据业务的特点,一天肯定不会有这么多条记录,这显然是错误的。问题是什么?仔细看SQL,发现没有加groupby,随便找了10条数据。mysql>selectbackup_datefromredis_backup_resultlimit10;+------------+|backup_date|+------------+|2018-08-14||2018-08-14||2018-08-14||2018-08-15||2018-08-15||2018-08-15||2018-08-15||2018-08-15||2018-08-15||2018-08-15|+------------+10rowsinset(0.00sec)早期版本中,数据库参数sql_mode默认为空,这部分不会检查,来自从语法角度是允许的;但是在更高的版本中是不支持的,比如5.7版本,所以解决方法很简单。添加groupby后,结果如预期。mysql>selectbackup_date,count(*)piece_nofromredis_backup_resultgroupbybackup_date;+------------+----------+|backup_date|piece_no|+-------------+------------+|2018-08-14|3||2018-08-15|121||2018-08-16|184||2018-08-17|3284||2018-08-18|7272||2018-08-19|7272||2018-08-20|7272||2018-08-21|7272||2018-08-22|8226|+-----------+------------+9rowsinset(0.06sec)但是我很好奇这个分析的逻辑。好像SQL解析了第一行然后输出了count(*)的操作,显然这是从执行计划中获取不到的信息。我们换个思路,看看这张表有4万多条记录。mysql>selectcount(*)fromredis_backup_result;+--------+|count(*)|+--------+|40944|+----------+1rowinset(0.01sec)为了验证,我们可以使用_rowid进行初步验证。InnoDB表中,如果没有默认主键,会生成一个6字节的自增长主键,可以用select_rowidfromtable查询,如下:mysql>select_rowidfromredis_backup_resultlimit5;+---------+|_rowid|+--------+|117||118||119||120||121|+--------+5rowsinset(0.00sec)可以实现一个初步的想法。mysql>select_rowid,count(*)fromredis_backup_result;+------+----------+|_rowid|count(*)|+--------+---------+|117|41036|+--------+----------+1rowinset(0.03sec)然后继续升华,借助rownum当然,这个特性在MySQL中是不原生支持的,需要间接实现。mysql>SELECT@rowno:=@rowno+1asrowno,r._rowidfromredis_backup_resultr,(select@rowno:=0)tlimit20;+------+-----+|rowno|_rowid|+-------+--------+|1|117||2|118||3|119||4|120||5|121||6|122||7|123||8|124||9|125||10|126||11|127||12|128||13|129||14|130||15|131||16|132||17|133||18|134||19|135||20|136|+-------+-------+20rowsinset(0.00sec)写一个完整的语句,如下:mysql>SELECT@rowno:=@rowno+1asrowno,r._rowid,backup_date,count(*)fromredis_backup_resultr,(select@rowno:=0)t;+--------+--------+------------+------------+|rowno|_rowid|backup_date|count(*)|+--------+--------+------------+----------+|1|117|2018-08-14|41061|+-------+------+------------+----------+1rowinset(0.02sec)通过这个案例,可以清楚的发现,然后计算第1行中的记录(*)。当然,我们的目标是掌握rowid和主键的一些关联,所以我们也回顾下主键使用中隐藏的问题。问题2:rowid和主键是什么关系?在学习MySQL开发规范的索引规范时,强调了一个重点:建议每张表都有主键。这里简单分析一下为什么?除了规范之外,在存储方式上,在InnoDB存储引擎中,表是按照主键顺序存储的。我们称它们为聚簇索引表或索引组织表(IOT)。表中主键的引用如下:(1)显式创建主键Primarykey。(2)判断表中是否存在非空唯一索引,如果存在则为主键。(3)如果以上条件都不满足,则生成一个UUID的隐式主键(6字节)。从上面可以看出,MySQL对于主键有一套维护机制,一些常用的索引也会有相应的影响,比如唯一索引、非唯一索引、覆盖索引等(层次索引),来自从存储的角度来看,主键列默认包含在二级索引列中。如果主键太长,二级索引会占用大量空间。问题三:主键的使用有哪些隐患?这导致了业界非常普遍的主键性能问题。这不是一个单一的问题。需要在MySQL的方向上不断转型,将技术价值和商业价值结合起来。我看到很多业务都设置了自增列,但大多数情况下,这个自增列并没有实际的业务意义。虽然主键列保证了id的唯一性,但是业务开发不能直接根据主键自增。要查询的列,所以他们需要寻找新的业务属性,添加一系列唯一索引,非唯一索引等,使我们遵守的规范与我们在业务中的使用方式存在偏差。从另一个维度来说,我们对主键的理解是有偏差的。不能简单的认为主键一定是从1开始的整数类型,需要结合业务场景来看。比如我们的身份证,其实是一张不错的。例如,证书编号分为几个部分,偏向于检索和维护;或者是在外出就餐时得到的流水号,这里面有一定的业务属性,对于我们了解业务的使用是一种很好的参考。问题四:如何认识rowid的潜在瓶颈并进行调试验证。我们知道rowid只有6个字节,所以最大值是2^48,所以row_id一旦超过这个值,还是会增加。这种情况下有什么隐患吗?我们可以做一个测试来说明,如果不练习假动作,只说说而已。1)我们创建一个没有任何索引的表test_inc。createtabletest_inc(idint)engine=innodb;2)通过ps-ef|grepmysql获取对应的进程号,使用gdb开始调试配置,切记!这应该是您自己的测试环境。[root@dev01mysql]#gdb-p3132-ex'pdict_sys->row_id=1'-batch[NewLWP3192][NewLWP3160][NewLWP3159][NewLWP3158][NewLWP3157][NewLWP3156][NewLWP3155][NewLWP354][NewLWP3154][NewLWP3155]][NewLWP3151][NewLWP3150][NewLWP3149][NewLWP3148][NewLWP3147][NewLWP3144][NewLWP3143][NewLWP3142][NewLWP3141][NewLWP3140][NewLWP3139][NewLWP3138][NewLWP3137][NewLWP3144][NewLWP3137][NewLWP313LWP][NewLWP3137][NewLWP313LWP4]New633LWP3LWP3[NewLWP3133][Threaddebuggingusinglibthread_dbenabled]0x00000031ed8df283inpollfrom/lib64/libc.so.6$1=13)我们做一个基本的测试,获取建表语句,确保测试符合预期。mysql>showcreatetabletest_inc\G******************************1.row*****************************Table:test_incCreateTable:CREATETABLE`test_inc`(`id`int(11)DEFAULT)ENGINE=InnoDBDEFAULTCHARSET=utf81rowinset(0.00sec)4)插入一些数据使rowid继续增加。mysql>insertintotest_incvalues(1),(2),(3);QueryOK,3rowsaffected(0.08sec)Records:3Duplicates:0Warnings:05)我们重新设置rowid,调整为2^48mysql>selectpower(2,48);+----------------+|power(2,48)|+----------------+|281474976710656|+------------------+1rowinset(0.00sec)[root@dev01mysql]#gdb-p3132-ex'pdict_sys->row_id=281474976710656'-batch...[Threaddebuggingusinglibthread_dbenabled]0x00000031ed8df283inpollfrom/lib64/libc.so.6$1=2814749767106566)继续写入一些数据,比如我们写入4、5、6行数据mysql>insertintotest_incvalues(4),(5),(6);QueryOK,3rowsaffected(0.07sec)Records:3Duplicates:0Warnings:07)查看数据结果,发现1、2行被覆盖了。mysql>select*fromtest_inc;+-----+|id|+-----+|4||5||6||3|+------+4rowsinset(0.00sec)由此可见,rowid自增后,仍然存在使用瓶颈。当然这个概率很低,需要自增的列的值达到了281万亿,这是一个非常大的值。从功能的角度来看,应该抛出写入重复值的错误更合理。有了主键,上面的瓶颈似乎就消失了。
