Python全栈(三)数据库优化之12.MySQL高级-数据库锁和数据库分区

一、数据库锁

锁是计算机协调多个进程或线程并发访问某一资源的机制。
根据对数据操作的粒度,分为表锁、行锁、间隙锁。

1.表锁

更偏读。
偏向MyISAM存储引擎,开销小,加锁快;
无死锁,因为整个表都被锁了,别人不能访问;
锁定粒度大,发送锁冲突的概率最高,并发度低。
表锁举例:
创建表并插入数据:

create table mylock(
    id int not null primary key auto_increment,
    name varchar(20)
)engine myisam;

insert into mylock(name) values('a');
insert into mylock(name) values('b');
insert into mylock(name) values('c');
insert into mylock(name) values('d');
insert into mylock(name) values('e');

打印

Query OK, 0 rows affected (0.01 sec)

Query OK, 1 row affected (0.01 sec)

Query OK, 1 row affected (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Query OK, 1 row affected (0.00 sec)

手动增加表锁:
语法:
lock table 表名字 read(write),表名字2 read(write);

lock table mylock read,book write;

打印

Query OK, 0 rows affected (0.01 sec)

查看所有数据库的所有表,同时可以查看创建的锁:

show open tables;

打印

+--------------------+------------------------------------------------------+--------+-------------+
| Database           | Table                                                | In_use | Name_locked |
+--------------------+------------------------------------------------------+--------+-------------+
| performance_schema | events_waits_summary_by_user_by_event_name           |      0 |           0 |
| performance_schema | events_waits_summary_global_by_event_name            |      0 |           0 |
| performance_schema | events_transactions_summary_global_by_event_name     |      0 |           0 |
| performance_schema | replication_connection_status                        |      0 |           0 |
| mysql              | time_zone_leap_second                                |      0 |           0 |
| mysql              | columns_priv                                         |      0 |           0 |
| performance_schema | metadata_locks                                       |      0 |           0 |
| performance_schema | status_by_user                                       |      0 |           0 |
| demo               | mylock                                               |      1 |           0 |
| mysql              | time_zone_transition_type                            |      0 |           0 |
| performance_schema | events_statements_current                            |      0 |           0 |
| performance_schema | prepared_statements_instances                        |      0 |           0 |
| performance_schema | events_statements_history_long                       |      0 |           0 |
| mysql              | db                                                   |      0 |           0 |
| performance_schema | file_instances                                       |      0 |           0 |
| performance_schema | events_stages_summary_by_user_by_event_name          |      0 |           0 |
| performance_schema | memory_summary_by_thread_by_event_name               |      0 |           0 |
...
| performance_schema | events_stages_history                                |      0 |           0 |
| mysql              | time_zone                                            |      0 |           0 |
| mysql              | slave_master_info                                    |      0 |           0 |
| mysql              | proxies_priv                                         |      0 |           0 |
| performance_schema | events_statements_summary_by_host_by_event_name      |      0 |           0 |
| demo               | book                                                 |      1 |           0 |
| performance_schema | socket_instances                                     |      0 |           0 |
| performance_schema | host_cache                                           |      0 |           0 |
| performance_schema | status_by_account                                    |      0 |           0 |
| performance_schema | events_statements_summary_by_account_by_event_name   |      0 |           0 |
| performance_schema | memory_summary_by_account_by_event_name              |      0 |           0 |
...
+--------------------+------------------------------------------------------+--------+-------------+
110 rows in set (0.00 sec)

显然,mylock表和book表的In_use值为1.
此时在同一命令行中读取

select * from mylock;

打印

+----+------+           
| id | name |           
+----+------+           
|  1 | a    |           
|  2 | b    |           
|  3 | c    |           
|  4 | d    |           
|  5 | e    |           
+----+------+           
5 rows in set (0.00 sec)

此时再打开一个命令行,执行相同的命令:

select * from mylock;

打印

+----+------+           
| id | name |           
+----+------+           
|  1 | a    |           
|  2 | b    |           
|  3 | c    |           
|  4 | d    |           
|  5 | e    |           
+----+------+           
5 rows in set (0.00 sec)

在命令行一中更新数据

update mylock set name = 'a2' where id = 1;

打印

ERROR 1099 (HY000): Table 'mylock' was locked with a READ lock and can't be updated

即读锁限制了对表的更新。
在命令行二中执行相同命令

update mylock set name = 'a2' where id = 1;

会发生堵塞,直到命令行一中解锁

unlock tables;

打印

Query OK, 0 rows affected (0.00 sec)

命令行二中打印

Query OK, 0 rows affected (57.79 sec)
Rows matched: 1  Changed: 0  Warnings: 0

即堵塞了57.79秒。
在命令行一中锁定mylock表之后,再在命令行二中查询其他表能正常查询,但是在命令行一中不能再查询其他表,否则会报错,要想读其他表,必须先对mylock解锁。
并且在命令行一关闭后,对mylock的锁自动关闭,其他命令行可对mylock表进行操作。

select * from book;

打印

ERROR 1100 (HY000): Table 'book' was not locked with LOCK TABLES

在命令行一中加写锁

lock table mylock write;

打印

Query OK, 0 rows affected (0.00 sec)

查询数据

select * from mylock;

打印

+----+------+           
| id | name |           
+----+------+           
|  1 | a2   |           
|  2 | b    |           
|  3 | c    |           
|  4 | d    |           
|  5 | e    |           
+----+------+           
5 rows in set (0.00 sec)

能正常查询数据。
在命令行二中执行相同命令:

select * from mylock;

发生堵塞。
在命令行二查询其他表能正常查询到。
在命令行一中更新数据

update mylock set name = 'a4' where id = 1;

打印

Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

在命令行二中执行相同操作

update mylock set name = 'a4' where id = 1;

也会发生堵塞,直到命令行一释放写锁。
释放表锁:
语法:
unlock tables;
表锁总结:
MyISAM在执行查询语句(select)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁:

  • 对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其他进行的写操作。即读锁会堵塞写,但是不会堵塞读。
  • 对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其他进程的读写操作。即写锁会堵塞写和读。
show status like 'table%';

打印

+----------------------------+-------+ 
| Variable_name              | Value | 
+----------------------------+-------+ 
| Table_locks_immediate      | 112   | 
| Table_locks_waited         | 0     | 
| Table_open_cache_hits      | 0     | 
| Table_open_cache_misses    | 0     | 
| Table_open_cache_overflows | 0     | 
+----------------------------+-------+ 
5 rows in set (0.01 sec)               

Table_locks_immediate表示产生表级锁的次数,Table_locks_waited表示表级锁发生冲突时等待的次数,这两个值较高,存在较高的表级锁竞争。

2.行锁

更偏写。
偏向InnoDB存储引擎,开销大,加锁慢,会出现死锁;
锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
InnoDB与MyISAM的最大不同点,是支持事务,采用了行级锁。
行锁举例:
创建表并插入数据

create table test_innodb_lock(a int(11),b varchar(16))engine=innodb;

insert into test_innodb_lock values(1,'b2');
insert into test_innodb_lock values(3,'3');
insert into test_innodb_lock values(4,'4000');
insert into test_innodb_lock values(5,'5000');

create index idx_test_innodb_a on test_innodb_lock(a);

create index idx_test_innodb_b on test_innodb_lock(b);

打印

Query OK, 0 rows affected (0.02 sec)  
                                      
Query OK, 1 row affected (0.01 sec)   
                                      
Query OK, 1 row affected (0.00 sec)   
                                      
Query OK, 1 row affected (0.00 sec)   
                                      
Query OK, 1 row affected (0.00 sec)   
                                      
Query OK, 0 rows affected (0.02 sec)  
Records: 0  Duplicates: 0  Warnings: 0
                                      
Query OK, 0 rows affected (0.02 sec)  
Records: 0  Duplicates: 0  Warnings: 0              

两个命令行关闭自动提交:

set autocommit = 0;

打印

Query OK, 0 rows affected (0.00 sec)            

在命令行一中更新数据:

update test_innodb_lock set b = '4001' where a = 4;

打印

Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0          

在命令行一中查询,

select * from test_innodb_lock;

打印

+------+------+
| a    | b    |
+------+------+
|    1 | b2   |
|    3 | 1024 |
|    4 | 4001 |
|    5 | 1024 |
+------+------+
4 rows in set (0.00 sec)

数据已经改变
此时在命令行二中查询,

select * from test_innodb_lock;

打印

+------+------+         
| a    | b    |         
+------+------+         
|    1 | b2   |         
|    3 | 1024 |         
|    4 | 1024 |         
|    5 | 1024 |         
+------+------+         
4 rows in set (0.01 sec) 

会发现未改变,需要在两个命令行中commit,然后再在命令行二中查询,

select * from test_innodb_lock;

打印

+------+------+         
| a    | b    |         
+------+------+         
|    1 | b2   |         
|    3 | 1024 |         
|    4 | 4001 |         
|    5 | 1024 |         
+------+------+         
4 rows in set (0.00 sec)

在命令行一中执行

update test_innodb_lock set b = '4002' where a = 4;

打印

Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0          

此时在命令行二中执行

update test_innodb_lock set b = '4003' where a = 4;

会发生堵塞
在命令行一中提交后,命令行二打印

Query OK, 1 row affected (1 min 17.33 sec)
Rows matched: 1  Changed: 1  Warnings: 0

此时两个命令行分别查询,值分别为4002、4003,出现不一致的情况,再在两个命令行同时提交后,再次查询,数据一致,均为4003。
两个命令行中修改不同行的数据,不会发生冲突。
varchar不加引号:

  • 索引失效,全表扫描;
  • 由行锁变为表锁,可能阻塞。

在命令行一中执行

update test_innodb_lock set a = 5 where b = 4000;

打印

Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0        

再在命令行二中执行

update test_innodb_lock set b = '4003' where a = 4;

会发生堵塞。

如何分析行锁定:

通过检查innodb_row_lock状态变量来分析系统上的行锁争夺情况。

show status like 'innodb_row_lock%';

打印

+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0     |
| Innodb_row_lock_time          | 77325 |
| Innodb_row_lock_time_avg      | 77325 |
| Innodb_row_lock_time_max      | 77325 |
| Innodb_row_lock_waits         | 1     |
+-------------------------------+-------+
5 rows in set (0.00 sec)      

各个状态量的说明

状态量 说明
Innodb_row_lock_current_waits 当前正在等待锁定的数量
Innodb_row_lock_time 从系统启动到现在锁定的总时间长度,值越大,锁定时间越长
Innodb_row_lock_time_avg 每次等待所花费平均时间
Innodb_row_lock_time_max 从系统启动到现在等待最长的一次所花费的时间
Innodb_row_lock_waits 系统启动后到现在总共等待的次数

3.间隙锁

在命令行一中:

update test_innodb_lock set b = '1024' where a > 1 and a < 6;

打印

Query OK, 1 row affected (0.00 sec)
Rows matched: 3  Changed: 1  Warnings: 0      

在命令行二中

insert into test_innodb_lock values(2,'2000');

发生堵塞,在命令行一中commit才能插入成功。
此即间隙锁。
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,innodb会给符合条件的已有数据记录的索引项加锁, 对于键值在条件范围内但并不存在的记录,叫做"间隙";
innodb也会对这个"间隙"加锁,这种锁机制就是所谓的间隙锁。
间隙锁的危害:

  • 因为SQL执行过程中通过范围查找的话,他会锁定整个范围内所有的索引值,即使这个键值并不存在;
  • 间隙锁有一个比较致命的弱点,就是当锁定以为范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。

如何锁定一行
select * from test_innodb_lock where a = 8 for update;

二、MySQL分区表

分区表的特点:
在逻辑上为一个表,在物理上存储在多个文件中。
查看MySQL是否支持分区:

show plugins;

打印

+----------------------------+----------+--------------------+---------+---------+
| Name                       | Status   | Type               | Library | License |
+----------------------------+----------+--------------------+---------+---------+
| binlog                     | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| mysql_native_password      | ACTIVE   | AUTHENTICATION     | NULL    | GPL     |
| sha256_password            | ACTIVE   | AUTHENTICATION     | NULL    | GPL     |
| CSV                        | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| MEMORY                     | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| InnoDB                     | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| INNODB_TRX                 | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_LOCKS               | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_LOCK_WAITS          | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
...
| MyISAM                     | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| MRG_MYISAM                 | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| PERFORMANCE_SCHEMA         | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| ARCHIVE                    | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| BLACKHOLE                  | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| FEDERATED                  | DISABLED | STORAGE ENGINE     | NULL    | GPL     |
| partition                  | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| ngram                      | ACTIVE   | FTPARSER           | NULL    | GPL     |
+----------------------------+----------+--------------------+---------+---------+
44 rows in set (0.01 sec)                                                               

创建表

create table `login_log`(
    login_id int(10) unsigned not null comment '登录用户id',
    login_time timestamp not null default current_timestamp,
    login_ip int(10) unsigned not null comment '登录类型'
)engine=innodb default charset=utf8 partition by hash(login_id) partitions 4;

打印

Query OK, 0 rows affected (0.06 sec)      

分为4个区,此时在数据文件中有4个文件login_log#p#p0.ibd、login_log#p#p1.ibd、login_log#p#p2.ibd、login_log#p#p3.ibd。
分区键:
分区引入了分区键的概念,分区键用于根据某个区间值、特定值、或者HASH函数值执行数据的聚集,让数据根据规则分布在不同的分区中。
分区类型:

  • RANGE分区
  • LIST分区
  • HASH分区

无论哪种分区类型,要么分区表上没有主键/唯一键,要么分区表的主键/唯一键都必须包括分区键,也就是说不能使用主键/唯一字段之外的其他字段分区。

RANGE分区:

RANGE分区特点:

  • 根据分区键值的范围把数据行存储到表的不同分区中;
  • 多个分区的范围要连续,但是不能重叠;
  • 分区不包括上限,取不到上限值。
    建立RANGE分区:
create table `login_log_range`(
    login_id int(10) unsigned not null comment '登录用户ID',
    login_time timestamp not null default CURRENT_TIMESTAMP,
    login_ip int(10) unsigned not null comment '登录ip'
)engine=innodb 
partition by range(login_id)(
partition p0 values less than(10000),   #实际范围0-9999
partition p1 values less than(20000),   #实际范围10000-19999
partition p2 values less than(30000),   #实际范围20000-29999
partition p3 values less than maxvalue  #存储大于30000的数据
);

打印

共 0 行受到影响

执行耗时   : 0.033 sec
传送时间   : 1.004 sec
总耗时      : 1.038 sec      

此时插入一条login_id大于30000的数据:

insert into login_log_range values(30001,now(),1)

打印

Query OK, 1 row affected (0.01 sec)      

分析SQL语句,查看分区

explain select * from login_log_range where login_id = 30001;

打印

+----+-------------+-----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table           | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | login_log_range | p3         | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+-----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)     

显示位于p3分区。
查看id为500的记录对应的分区:

explain select * from login_log_range where login_id = 500;

打印

+----+-------------+-----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table           | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | login_log_range | p0         | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+-----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)     

显示位于p0分区。
查看所有分区:

explain select * from login_log_range;

打印

+----+-------------+-----------------+-------------+------+---------------+------+---------+------+------+----------+-------+ 
| id | select_type | table           | partitions  | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra | 
+----+-------------+-----------------+-------------+------+---------------+------+---------+------+------+----------+-------+ 
|  1 | SIMPLE      | login_log_range | p0,p1,p2,p3 | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | NULL  | 
+----+-------------+-----------------+-------------+------+---------------+------+---------+------+------+----------+-------+ 
1 row in set, 1 warning (0.01 sec)                                                                                               

RANGE分区使用场景:

  • 分区键为日期或是时间类型;
  • 经常运行包含分区键的查询,MySQL可以很快的确定只有某一个或某些分区需要扫描,例如检索商品login_id小于10000的记录数,MySQL只需要扫描p0分区即可;
  • 定期按分区范围清理历史数据。

删除分区:

alter table login_log_range drop partition p0;

打印

Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0  

HASH分区

HASH分区的特点:

  • 根据MOD(分区键,分区值)的值把数据行存储到表的不同分区内;
  • 数据可以平均地分布在各个分区中;
  • HASH分区的键值必须是一个INT类型的值,或是通过函数可以转为INT类型。

建立HASH分区表:

create table `login_log_hash`(
    login_id int(10) unsigned not null comment '登录用户ID',
    login_time timestamp not null default CURRENT_TIMESTAMP,
    login_ip int(10) unsigned not null comment '登录ip'
)engine=innodb default charset=utf8 partition by hash(login_id) partitions 4;

或者

create table `login_log_hash`(
    login_id int(10) unsigned not null comment '登录用户ID',
    login_time timestamp not null default CURRENT_TIMESTAMP,
    login_ip int(10) unsigned not null comment '登录ip'
)engine=innodb default charset=utf8 partition by hash(UNIX_TIMESTAMP(login_time)) partitions 4;

打印

执行耗时   : 0.031 sec
传送时间   : 1.005 sec
总耗时      : 1.037 sec

插入数据

insert into login_log_hash values(1,now(),1);
insert into login_log_hash values(2,now(),2);
insert into login_log_hash values(3,now(),3);
insert into login_log_hash values(4,now(),4);
insert into login_log_hash values(5,now(),5);

打印

Query OK, 1 row affected (0.00 sec)
                                   
Query OK, 1 row affected (0.00 sec)
                                   
Query OK, 1 row affected (0.00 sec)
                                   
Query OK, 1 row affected (0.00 sec)
                                   
Query OK, 1 row affected (0.00 sec)

查看分区

explain select * from login_log_hash where login_id = 1;
explain select * from login_log_hash where login_id = 2;
explain select * from login_log_hash where login_id = 3;
explain select * from login_log_hash where login_id = 4;
explain select * from login_log_hash where login_id = 5;

打印

+----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table          | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | login_log_hash | p1         | ALL  | NULL          | NULL | NULL    | NULL |    2 |    50.00 | Using where |
+----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)                                                                                               
                                                                                                                                 
+----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table          | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | login_log_hash | p2         | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)                                                                                               
                                                                                                                                 
+----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table          | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | login_log_hash | p3         | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)                                                                                               
                                                                                                                                 
+----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table          | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | login_log_hash | p0         | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)                                                                                               
                                                                                                                                 
+----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table          | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | login_log_hash | p1         | ALL  | NULL          | NULL | NULL    | NULL |    2 |    50.00 | Using where |
+----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)                                                                                                                                                                                      

显然,每条数据位于对应的分区。
查看所有分区:

explain select * from login_log_hash;

打印

+----+-------------+----------------+-------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table          | partitions  | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+----------------+-------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | login_log_hash | p0,p1,p2,p3 | ALL  | NULL          | NULL | NULL    | NULL |    4 |   100.00 | NULL  |
+----+-------------+----------------+-------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

hash分区为全表扫描。

LIST分区

LIST分区特点:

  • 按分区键取值的列表进行分区;
  • 同范围分区一样,各分区的列表值不能重复;
  • 每一行数据必须能找到对应的分区列表,否则数据插入失败。
    建立LIST分区
create table `login_log_list`(
    login_id int(10) unsigned not null comment '登录用户ID',
    login_time timestamp not null default CURRENT_TIMESTAMP,
    login_ip int(10) unsigned not null comment '登录ip',
    login_type int(10) not null
)engine=innodb 
partition by list(login_type)(
partition p0 values in(1,3,5,7,9),    
partition p1 values in(2,4,6,8)   
);

打印

Query OK, 0 rows affected (0.03 sec)

插入值

insert into login_log_list values(1,now(),2,1);

打印

Query OK, 1 row affected (0.00 sec)

可以正常插入。
插入一条login_type列表中没有的数据

insert into login_log_list values(1,now(),2,10);

打印

ERROR 1526 (HY000): Table has no partition for value 10

会报错,即没有10对应的分区。

三、练习–如何选择合适的分区方式

业务场景:

  • 用户每次登录都会记录到日志表中;
  • 用户登录日志保存一年,一年后可以删除。
create table login_lg_log(
    login_id int(10) not null,
    login_time datetime not null default current_timestamp,
    login_ip int(10) not null
)engine = INNODB
partition by range(year(login_time))(
    partition p0 values less than(2015),
    partition p1 values less than(2016),
    partition p2 values less than(2017),
    partition p3 values less than(2018)
);

打印

Query OK, 0 rows affected (0.03 sec)

插入数据

insert into login_lg_log values
(1,'2015-01-01',1),
(2,'2015-01-02',1),
(3,'2016-01-01',1),
(4,'2016-01-01',1),
(5,'2017-01-01',1),
(6,'2017-01-01',1),
(7,'2017-01-01',1)
;

打印

Query OK, 7 rows affected (0.00 sec)
Records: 7  Duplicates: 0  Warnings: 0

查看分区

select table_name,partition_name,partition_description,table_rows from information_schema.partitions where table_name = 'login_lg_log';

打印

+--------------+----------------+-----------------------+------------+
| table_name   | partition_name | partition_description | table_rows |
+--------------+----------------+-----------------------+------------+
| login_lg_log | p0             | 2015                  |          0 |
| login_lg_log | p1             | 2016                  |          2 |
| login_lg_log | p2             | 2017                  |          2 |
| login_lg_log | p3             | 2018                  |          3 |
+--------------+----------------+-----------------------+------------+
4 rows in set (0.00 sec)

添加分区

alter table login_lg_log add partition(partition p4 values less than(2019));

打印

Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

删除分区

alter table login_lg_log drop partition p0;

打印

Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

数据归档:

新建一个和要归档的表的结构一样的表:

create table login_lg_log_test(
    login_id int(10) not null,
    login_time datetime not null default current_timestamp,
    login_ip int(10) not null
)engine = INNODB;

打印

Query OK, 0 rows affected (0.01 sec)

分区迁移:

alter table login_lg_log exchange partition p1 with table login_lg_log_test;

打印

Query OK, 0 rows affected (0.01 sec)

查看两个表:

select * from login_lg_log;

打印

+----------+---------------------+----------+
| login_id | login_time          | login_ip |
+----------+---------------------+----------+
|        3 | 2016-01-01 00:00:00 |        1 |
|        4 | 2016-01-01 00:00:00 |        1 |
|        5 | 2017-01-01 00:00:00 |        1 |
|        6 | 2017-01-01 00:00:00 |        1 |
|        7 | 2017-01-01 00:00:00 |        1 |
+----------+---------------------+----------+
5 rows in set (0.00 sec)                     

即原表中p1分区的数据被移除。

select * from login_lg_log_test;

打印

+----------+---------------------+----------+
| login_id | login_time          | login_ip |
+----------+---------------------+----------+
|        1 | 2015-01-01 00:00:00 |        1 |
|        2 | 2015-01-02 00:00:00 |        1 |
+----------+---------------------+----------+
2 rows in set (0.00 sec)

新表中即为原表p1分区的数据。
使用分区表的注意事项:

  • 结合业务场景选择分区键,避免跨分区查询;
  • 对分区表进行查询最好在where从句中包含分区键;
  • 具有主键或唯一索引的表,主键或唯一索引必须是分区键的一部分。
发布了50 篇原创文章 · 获赞 184 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/CUFEECR/article/details/104008799