一、mysql 如何给大表添加字段
1.能不加字段就不要加,能不修改字段就不要修改,能不删除字段就不要删除,等等为什么要删除字段呢?如果没事,不要蛋疼的找事。实际上,我们那次更新失败后,我们并没有增加那个字段,然后我们一直运行到今天,但是后来还是增加了其他字段
2.增加字段的情况下,如果可以通过增加一个新的表来增加这个字段,那么就增加一个新的表,通过cache或程序来实现join的效果
3.如果能停机,并且停机的时间在你容忍范围之内,数据备份之后停机来做。如果是主从备份,做这样大的操作的时候,停掉主从备份,万一你挂了,备份数据库可以救你。等到一切安全了,重启主从备份;
4.如果上面都不行,这个字段还是要改,要加,需要用到下面的方法,也是扇贝网正在使用的方法;
修改大数据表的方法:
1.被修改的表 Table A需要有一个记录时间戳的字段,这个时间戳就是每次数据更新,都会更新的字段,这个字段需要有索引,在django里可以使用 auto_now=True
2.创建一个新的临时表 Table B,不是tmp_table,是一个新的表,但是是临时使用的。这个表和要修改的表拥有一模一样的数据结构,加上你要修改的部分,比如增加的字段;
3.记录下Table A的索引
4.删除 Table B的全部索引
5.把Table A的数据全部复制到Table B,是不是执行 INSERT INTO B(field1, field2) SELECT field1, field2 FROM A?当然不是,这么做不还是锁死了Table A么,这里的迁移就是一个需要细分的地方,需要写一个脚本,让程序每次读取比如5000条数据出来,插入到Table B里面,因为Table B是没有索引的,所以要当心不要使用多进程来做;如果是多进程,要确保插入到B的时候是不会有重复数据的;如果是1000万的数据,每次5000条,假设这个操作需要500ms,那么 2000*200ms= 16分钟。这只是一个估值,具体情况和服务器当时的情况有关,不好细说。另外,我们要记录这个迁移开始的时间点,记为t1;
6.那么这个时候Table A的数据是不是都进入了Table B呢,应当说差不多大部分都进入了,但5中说,这大概需要16分钟,这么长的时间里,可能有新的数据进入了,也有可能已有的数据发生了更新,所以我们要把Table A中在t1之后发生变化的数据查找出来,然后更新到Table B中,我们的做法是:
记录这个操作对应的时间点 t2
BEGIN;
DELETE FROM B WHERE updated_time> t1;
INSERT INTO B(field1, field2) SELECT field1, field2 FROM A WHERE updated_time>t1;
COMMIT;
7.现在A和 B差不多该同步了吧?差不多了,但是6执行完之后, A仍然在写,子子孙孙无穷尽也...,但这个时候 A和 B的差异已经非常非常小了,所以在下一步,我们在一个transaction里执行下面的操作:
BEGIN;
DELETE FROM B WHERE updated_time> t2;
INSERT INTO B(field1, field2) SELECT field1, field2 FROM A WHERE updated_time>t2;
ALTER TABLE A RENAME TO C;
ALTER TABLE B RENAME TO A;
COMMIT;
8. Done
PS:如果A表没有时间戳,实际上没有先见之明的人大概不会想到要预留一个时间戳的字段,那么几乎是没有办法快速比较两个表的差异的,这个时候我选择的做法就是放弃修改的数据,只管新的数据了,所以只要把t1, t2换成id就可以了,这样delete语句也省略了,因为没啥好delete的;
千万不要想着根据ID来JOIN然后更新B表的字段来补齐新的数据,如果能把两个千万级别的表JOIN起来,内存有多大呢?
9.上面的解决方案是我们第二次尝试之后犯下了一个巨大的错误,这个错误导致网站瘫痪了大概20分钟,如果你和我一样没有发现问题,那么这就是悲剧的地方。问题就在于我是根据上面的PS来操作的,然后B就华丽地变成了A. B表至今身上是没有索引的,立即悲剧。所以应当在第5步之后按照A的索引为B建立索引,待索引全部好了之后,再继续6。如果不是走PS这条路,而是有时间戳的字段的话,在6的时候会发现这个问题,因为那条Delete慢的超出想像,会明白这里是有问题的
10.新手,请在本地练习之后,再实际操作;可以多操作几次,写一个脚本,服务器上直接执行脚本.
二、如何向mysql中插入date 字段
以每24小时作为一份时间(而非自然日),根据用户的配置有两种工作模式:带状模式中,用户仅定义开始日期时,从开始日期(含)开始,每份时间1个分片地无限增加下去;环状模式中,用户定义了开始日期和结束日期时,以结束日期(含)和开始日期(含)之间的时间份数作为分片总数(分片数量固定),以类似取模的方式路由到这些分片里。
1. DBLE启动时,读取用户在 rule.xml配置的 sBeginDate来确定起始时间
2.读取用户在 rule.xml配置的 sPartionDay来确定每个 MySQL分片承载多少天内的数据
3.读取用户在 rule.xml配置的 dateFormat来确定分片索引的日期格式
4.在 DBLE的运行过程中,用户访问使用这个算法的表时,WHERE子句中的分片索引值(字符串),会被提取出来尝试转换成 Java内部的时间类型
5.然后求分片索引值与起始时间的差,除以 MySQL分片承载的天数,确定所属分片
1. DBLE启动时,读取用户在 rule.xml配置的起始时间 sBeginDate、终止时间 sEndDate和每个 MySQL分片承载多少天数据 sPartionDay
2.根据用户设置,建立起以 sBeginDate开始,每 sPartionDay天一个分片,直到 sEndDate为止的一个环,把分片串联串联起来
3.读取用户在 rule.xml配置的 defaultNode
4.在 DBLE的运行过程中,用户访问使用这个算法的表时,WHERE子句中的分片索引值(字符串),会被提取出来尝试转换成 Java内部的日期类型
5.然后求分片索引值与起始日期的差:如果分片索引值不早于 sBeginDate(哪怕晚于 sEndDate),就以 MySQL分片承载的天数为模数,对分片索引值求模得到所属分片;如果分片索引值早于 sBeginDate,就会被放到 defaultNode分片上
与MyCat的类似分片算法对比
中间件
DBLE
MyCat
分片算法种类 date分区算法按日期(天)分片
两种中间件的取模范围分片算法使用上无差别
开发注意点
【分片索引】1.必须是字符串,而且 java.text.SimpleDateFormat能基于用户指定的 dateFormat来转换成 java.util.Date
【分片索引】2.提供带状模式和环状模式两种模式
【分片索引】3.带状模式以 sBeginDate(含)起,以 86400000毫秒(24小时整)为一份,每 sPartionDay份为一个分片,理论上分片数量可以无限增长,但是出现 sBeginDate之前的数据而且没有设定 defaultNode的话,会路由失败(如果有 defaultNode,则路由至 defaultNode)
【分片索引】4.环状模式以 86400000毫秒(24小时整)为一份,每 sPartionDay份为一个分片,以 sBeginDate(含)到 sEndDate(含)的时间长度除以单个分片长度得到恒定的分片数量,但是出现 sBeginDate之前的数据而且没有设定 defaultNode的话,会路由失败(如果有 defaultNode,则路由至 defaultNode)
【分片索引】5.无论哪种模式,分片索引字段的格式化字符串 dateFormat由用户指定
【分片索引】6.无论哪种模式,划分不是以日历时间为准,无法对应自然月和自然年,且会受闰秒问题影响
运维注意点
【扩容】1.带状模式中,随着 sBeginDate之后的数据出现,分片数量的增加无需再平衡
【扩容】2.带状模式没有自动增添分片的能力,需要运维手工提前增加分片;如果路由策略计算出的分片并不存在时,会导致失败
【扩容】3.环状模式中,如果新旧 [sBeginDate,sEndDate]之间有重叠,需要进行部分数据迁移;如果新旧 [sBeginDate,sEndDate]之间没有重叠,需要数据再平衡
配置注意点
【配置项】1.在 rule.xml中,可配置项为<propertyname="sBeginDate">、<propertyname="sPartionDay">、<propertyname="dateFormat">、<propertyname="sEndDate">和<propertyname="defaultNode">
【配置项】2.在 rule.xml中配置<propertyname="dateFormat">,符合 java.text.SimpleDateFormat规范的字符串,用于告知 DBLE如何解析sBeginDate和sEndDate
【配置项】3.在 rule.xml中配置<propertyname="sBeginDate">,必须是符合 dateFormat的日期字符串
【配置项】4.在 rule.xml中配置<propertyname="sEndDate">,必须是符合 dateFormat的日期字符串;配置了该项使用的是环状模式,若没有配置该项则使用的是带状模式
【配置项】5.在 rule.xml中配置<propertyname="sPartionDay">,非负整数,该分片策略以 86400000毫秒(24小时整)作为一份,而 sPartionDay告诉 DBLE把每多少份放在同一个分片
【配置项】6.在 rule.xml中配置<propertyname="defaultNode">标签,非必须配置项,不配置该项的话,用户的分片索引值没落在 mapFile定义
三、mysql中datetime字段怎么增加一个固定时间
PERIOD_DIFF(P1,P2)
返回在时期P1和P2之间月数,P1和P2应该以格式YYMM或YYYYMM。注意,时期参数P1和P2不是日期值。
mysql> select PERIOD_DIFF(9802,199703);
-> 11
DATE_ADD(date,INTERVAL expr type)
DATE_SUB(date,INTERVAL expr type)
ADDDATE(date,INTERVAL expr type)
SUBDATE(date,INTERVAL expr type)
这些功能执行日期运算。对于MySQL 3.22,他们是新的。ADDDATE()和SUBDATE()是DATE_ADD()和DATE_SUB()的同义词。
在MySQL 3.23中,你可以使用+和-而不是DATE_ADD()和DATE_SUB()。(见例子)date是一个指定开始日期的
DATETIME或DATE值,expr是指定加到开始日期或从开始日期减去的间隔值一个表达式,expr是一个字符串;它可以以
一个“-”开始表示负间隔。type是一个关键词,指明表达式应该如何被解释。EXTRACT(type FROM date)函数从日期
中返回“type”间隔。下表显示了type和expr参数怎样被关联: type值含义期望的expr格式
SECOND秒 SECONDS
MINUTE分钟 MINUTES
HOUR时间 HOURS
DAY天 DAYS
MONTH月 MONTHS
YEAR年 YEARS
MINUTE_SECOND分钟和秒"MINUTES:SECONDS"
HOUR_MINUTE小时和分钟"HOURS:MINUTES"
DAY_HOUR天和小时"DAYS HOURS"
YEAR_MONTH年和月"YEARS-MONTHS"
HOUR_SECOND小时,分钟,"HOURS:MINUTES:SECONDS"
DAY_MINUTE天,小时,分钟"DAYS HOURS:MINUTES"
DAY_SECOND天,小时,分钟,秒"DAYS HOURS:MINUTES:SECONDS"
MySQL在expr格式中允许任何标点分隔符。表示显示的是建议的分隔符。如果date参数是一个DATE值并且你的计算仅仅
包含YEAR、MONTH和DAY部分(即,没有时间部分),结果是一个DATE值。否则结果是一个DATETIME值。
mysql> SELECT"1997-12-31 23:59:59"+ INTERVAL 1 SECOND;
-> 1998-01-01 00:00:00
mysql> SELECT INTERVAL 1 DAY+"1997-12-31";
-> 1998-01-01
mysql> SELECT"1998-01-01"- INTERVAL 1 SECOND;
-> 1997-12-31 23:59:59
mysql> SELECT DATE_ADD("1997-12-31 23:59:59",
INTERVAL 1 SECOND);
-> 1998-01-01 00:00:00
mysql> SELECT DATE_ADD("1997-12-31 23:59:59",
INTERVAL 1 DAY);
-> 1998-01-01 23:59:59
mysql> SELECT DATE_ADD("1997-12-31 23:59:59",
INTERVAL"1:1" MINUTE_SECOND);
-> 1998-01-01 00:01:00
mysql> SELECT DATE_SUB("1998-01-01 00:00:00",
INTERVAL"1 1:1:1" DAY_SECOND);
-> 1997-12-30 22:58:59
mysql> SELECT DATE_ADD("1998-01-01 00:00:00",
INTERVAL"-1 10" DAY_HOUR);
-> 1997-12-30 14:00:00
mysql> SELECT DATE_SUB("1998-01-02", INTERVAL 31 DAY);
-> 1997-12-02
mysql> SELECT EXTRACT(YEAR FROM"1999-07-02");
-> 1999
mysql> SELECT EXTRACT(YEAR_MONTH FROM"1999-07-02 01:02:03");
-> 199907
mysql> SELECT EXTRACT(DAY_MINUTE FROM"1999-07-02 01:02:03");
-> 20102