1.什么是Cardinality?
Cardinality中文译名为:基数。
它在数据库中表示的意思就是数据库中某个表的某个列中不重复行的总个数。
例如下表:t
CREATE TABLE `t` (
`a` int(11) NOT NULL,
`b` varchar(800) DEFAULT NULL,
`c` int(11) NOT NULL,
PRIMARY KEY (`a`),
KEY `idx_c` (`c`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| a | int(11) | NO | PRI | NULL | |
| b | varchar(800) | YES | | NULL | |
| c | int(11) | NO | MUL | NULL | |
+-------+--------------+------+-----+---------+-------+
其中的数据有:
+----+--------+-----+
| a | b | c |
+----+--------+-----+
| 3 | yanjd3 | -3 |
| 4 | yanjd4 | -4 |
| 5 | yanjd5 | -5 |
| 6 | yanjd6 | -6 |
| 7 | yanjd6 | -7 |
| 8 | yanjd6 | -8 |
| 9 | yanjd6 | -9 |
| 10 | yanjd6 | -10 |
| 11 | yanjd6 | -11 |
| 12 | yanjd6 | -12 |
| 13 | yanjd6 | -13 |
| 14 | yanjd6 | -14 |
| 15 | yanjd6 | -15 |
| 16 | yanjd6 | -16 |
| 17 | yanjd6 | -17 |
| 18 | yanjd6 | -18 |
| 20 | yanjd6 | -20 |
| 21 | yanjd2 | -21 |
| 22 | yanjd3 | -22 |
| 23 | yanjd4 | -23 |
| 24 | yanjd5 | -24 |
| 25 | yanjd6 | -25 |
| 26 | yanjd6 | -26 |
| 27 | yanjd6 | -27 |
+----+--------+-----+
我们可以看出:
列a的基数为:24
列b的基数为:5
列c的基数为:24
2.如何在Mysql中统计普通列的基数?
显然易见,我们不可能每次都用眼睛看着数来确定某一列的基数。
在MySQL中可以用下面语句来确定某一列的基数:
以前面的表为例,统计出列a的基数:
mysql> select count(distinct a) from t;
+-------------------+
| count(distinct a) |
+-------------------+
| 24 |
+-------------------+
1 row in set (0.00 sec)
统计b列的基数:
mysql> select count(distinct b) from t;
+-------------------+
| count(distinct b) |
+-------------------+
| 5 |
+-------------------+
统计c列的基数:
mysql> select count(distinct c) from t;
+-------------------+
| count(distinct c) |
+-------------------+
| 24 |
+-------------------+
1 row in set (0.00 sec)
3.基数的用处
关于基数的用处只需记住一点:对于索引列,基数越大,查询效果越好,基数越小,查询效果越差,理想的索引列满足: 基数/实际行=1;
对于索引列,我们可以通过下面语句查看其基数:
mysql> show indexes from t \G;
从这里我们可以看到,对于索引列,数据库会自动对其基数进行统计。
4.InnoDB是怎样为索引列统计基数?
在生产环境中,索引的更新操作可能会非常频繁,如果每次索引发生变化时对其基数进行统计,那么将会给数据库带来很大的负担。
这是不能接受的,所以MySQL的策略是:
1)当表中索引列1/16的数据发生变化时,会对其基数进行统计。
2)stat_modified_counter>2000000000时(如果表中某一行的数据频繁地进行更新,这时表中的数据实际并没有增加,发生变化的还是这一行数据,这种情况下第一种策略就无法生效,所以MySQL设置了一个计时器,用来表示发生变化的次数,当发生变化的次数大于2000000000时进行统计),会对其基数进行统计。
注意:如果一张表非常大,那么实际统计一次基数信息所需要的时间可能会非常长,所以MySQL采用采样的方式进行基数的统计。
InnoDB存储引擎只对8个节点进行采样来统计基数,具体步骤如下:
首先,取得B+树索中节点的数量,记为A;
然后,随机取B+树索引中的8个节点,统计每页不同记录的个数,记为P1,P2,P3…P8;
最后计算出此索引列的基数=(P1+P2+P3+P4…+P8)*A/8
注意:上面获取8个节点的方式是随机!!这就意味着,统计的基数信息每次可能不同,这是正常的现象。
5.动手试一试
还是以上面的表t为例子
首先查看其索引列a的基数:
然后插入数据:
INSERT INTO T (a,b,c) values(28,'yanjd2',-21);
INSERT INTO T (a,b,c) values(29,'yanjd3',-22);
INSERT INTO T (a,b,c) values(30,'yanjd4',-23);
INSERT INTO T (a,b,c) values(31,'yanjd5',-24);
INSERT INTO T (a,b,c) values(32,'yanjd6',-25);
INSERT INTO T (a,b,c) values(33,'yanjd6',-26);
INSERT INTO T (a,b,c) values(34,'yanjd6',-27);
然后,查看其索引基数:
这时发现,其基数统计信息没发生变化啊?? 这可能是统计触发条件没达到的原因。
解决方案:我们可以执行下面语句使mysql主动计算统计信息:
analyze table t;//主动让mysql计算统计信息
这时可以发现,索引a的基数信息发生变化了。