精华内容
下载资源
问答
  • 如何优化MySQL千万级大表,我了6000的解读

    万次阅读 多人点赞 2019-10-21 20:03:03
    千万级大表如何优化,这是一个很有技术含量的问题,通常我们的直觉思维都会跳转到拆分或者数据分区,在此我想做一些补充和梳理,想和大家做一些这方面的经验总结,也欢迎大家提出建议。 从一开始脑海里开始也是...

    这是学习笔记的第 2138 篇文章

      640?wx_fmt=gif

    千万级大表如何优化,这是一个很有技术含量的问题,通常我们的直觉思维都会跳转到拆分或者数据分区,在此我想做一些补充和梳理,想和大家做一些这方面的经验总结,也欢迎大家提出建议。 

    从一开始脑海里开始也是火光四现,到不断的自我批评,后来也参考了一些团队的经验,我整理了下面的大纲内容。

    640?wx_fmt=png

    既然要吃透这个问题,我们势必要回到本源,我把这个问题分为三部分:

    “千万级”,“大表”,“优化”,

    也分别对应我们在图中标识的

    “数据量”,“对象”和“目标”。

    我来逐步展开说明一下,从而给出一系列的解决方案。 

    1.数据量:千万级

    千万级其实只是一个感官的数字,就是我们印象中的数据量大。 这里我们需要把这个概念细化,因为随着业务和时间的变化,数据量也会有变化,我们应该是带着一种动态思维来审视这个指标,从而对于不同的场景我们应该有不同的处理策略。

     

    1) 数据量为千万级,可能达到亿级或者更高

    通常是一些数据流水,日志记录的业务,里面的数据随着时间的增长会逐步增多,超过千万门槛是很容易的一件事情。

    2) 数据量为千万级,是一个相对稳定的数据量

    如果数据量相对稳定,通常是在一些偏向于状态的数据,比如有1000万用户,那么这些用户的信息在表中都有相应的一行数据记录,随着业务的增长,这个量级相对是比较稳定的。

    3) 数据量为千万级,不应该有这么多的数据

    这种情况是我们被动发现的居多,通常发现的时候已经晚了,比如你看到一个配置表,数据量上千万;或者说一些表里的数据已经存储了很久,99%的数据都属于过期数据或者垃圾数据。

    数据量是一个整体的认识,我们需要对数据做更近一层的理解,这就可以引出第二个部分的内容。 

    2.对象:数据表

    数据操作的过程就好比数据库中存在着多条管道,这些管道中都流淌着要处理的数据,这些数据的用处和归属是不一样的。

    一般根据业务类型把数据分为三种:

    (1)流水型数据

    流水型数据是无状态的,多笔业务之间没有关联,每次业务过来的时候都会产生新的单据,比如交易流水、支付流水,只要能插入新单据就能完成业务,特点是后面的数据不依赖前面的数据,所有的数据按时间流水进入数据库。

    (2)状态型数据

    状态型数据是有状态的,多笔业务之间依赖于有状态的数据,而且要保证该数据的准确性,比如充值时必须要拿到原来的余额,才能支付成功。

    (3)配置型数据

    此类型数据数据量较小,而且结构简单,一般为静态数据,变化频率很低。

    至此,我们可以对整体的背景有一个认识了,如果要做优化,其实要面对的是这样的3*3的矩阵,如果要考虑表的读写比例(读多写少,读少写多...),那么就会是3*3*4=24种,显然做穷举是不显示的,而且也完全没有必要,可以针对不同的数据存储特性和业务特点来指定不同的业务策略。 

    对此我们采取抓住重点的方式,把常见的一些优化思路梳理出来,尤其是里面的核心思想,也是我们整个优化设计的一把尺子,而难度决定了我们做这件事情的动力和风险。

    数据量增长情况

    数据表类型

    业务特点

    优化核心思想

    优化难度

    数据量为千万级,是一个相对稳定的数据量

    状态表

    OLTP业务方向

    能不拆就不拆读需求水平扩展

    ****

    数据量为千万级,可能达到亿级或者更高

    流水表

    OLTP业务的历史记录

    业务拆分,面向分布式存储设计

    ****

    OLAP业务统计数据源

    设计数据统计需求存储的分布式扩展

    ***

    数据量为千万级,不应该有这么多的数据

    配置表

    通用业务

    小而简,避免大一统

    *

    而对于优化方案,我想采用面向业务的维度来进行阐述。 

    3.目标:优化

    在这个阶段,我们要说优化的方案了,总结的有点多,相对来说是比较全了。

    整体分为五个部分:

    640?wx_fmt=png

    其实我们通常所说的分库分表等方案只是其中的一小部分,如果展开之后就比较丰富了。

    640?wx_fmt=png

    其实不难理解,我们要支撑的表数据量是千万级别,相对来说是比较大了,DBA要维护的表肯定不止一张,如何能够更好的管理,同时在业务发展中能够支撑扩展,同时保证性能,这是摆在我们面前的几座大山。

    我们分别来说一下这五类改进方案:

    优化设计方案1.规范设计

    在此我们先提到的是规范设计,而不是其他高大上的设计方案。

    黑格尔说:秩序是自由的第一条件。在分工协作的工作场景中尤其重要,否则团队之间互相牵制太多,问题多多。

    规范设计我想提到如下的几个规范,其实只是属于开发规范的一部分内容,可以作为参考。

    640?wx_fmt=png

    规范的本质不是解决问题,而是有效杜绝一些潜在问题,对于千万级大表要遵守的规范,我梳理了如下的一些细则,基本可以涵盖我们常见的一些设计和使用问题,比如表的字段设计不管三七二十一,都是varchar(500),其实是很不规范的一种实现方式,我们来展开说一下这几个规范。

    1)配置规范

    (1)MySQL数据库默认使用InnoDB存储引擎。

    (2)保证字符集设置统一,MySQL数据库相关系统、数据库、表的字符集使都用UTF8,应用程序连接、展示等可以设置字符集的地方也都统一设置为UTF8字符集。

    注:UTF8格式是存储不了表情类数据,需要使用UTF8MB4,可在MySQL字符集里面设置。在8.0中已经默认为UTF8MB4,可以根据公司的业务情况进行统一或者定制化设置。

    (3)MySQL数据库的事务隔离级别默认为RR(Repeatable-Read),建议初始化时统一设置为RC(Read-Committed),对于OLTP业务更适合。

    (4)数据库中的表要合理规划,控制单表数据量,对于MySQL数据库来说,建议单表记录数控制在2000W以内。

    (5)MySQL实例下,数据库、表数量尽可能少;数据库一般不超过50个,每个数据库下,数据表数量一般不超过500个(包括分区表)。

    2)建表规范

    (1)InnoDB禁止使用外键约束,可以通过程序层面保证。

    (2)存储精确浮点数必须使用DECIMAL替代FLOAT和DOUBLE。

    (3)整型定义中无需定义显示宽度,比如:使用INT,而不是INT(4)。

    (4)不建议使用ENUM类型,可使用TINYINT来代替。

    (5)尽可能不使用TEXT、BLOB类型,如果必须使用,建议将过大字段或是不常用的描述型较大字段拆分到其他表中;另外,禁止用数据库存储图片或文件。

    (6)存储年时使用YEAR(4),不使用YEAR(2)。

    (7)建议字段定义为NOT NULL。

    (8)建议DBA提供SQL审核工具,建表规范性需要通过审核工具审核后

    3)命名规范

    (1)库、表、字段全部采用小写。

    (2)库名、表名、字段名、索引名称均使用小写字母,并以“_”分割。

    (3)库名、表名、字段名建议不超过12个字符。(库名、表名、字段名支持最多64个字符,但为了统一规范、易于辨识以及减少传输量,统一不超过12字符)

    (4)库名、表名、字段名见名知意,不需要添加注释。

    对于对象命名规范的一个简要总结如下表4-1所示,供参考。

    命名列表

    对象中文名称

    对象英文全称

    MySQL对象简写

    视图

    view

    view_

    函数

    function

    func_

    存储过程

    procedure

    proc_

    触发器

    trigger

    trig_

    普通索引

    index

    idx_

    唯一索引

    unique index

    uniq_

    主键索引

    primary key

    pk_

    4)索引规范

    (1)索引建议命名规则:idx_col1_col2[_colN]、uniq_col1_col2[_colN](如果字段过长建议采用缩写)。

    (2)索引中的字段数建议不超过5个。

    (3)单张表的索引个数控制在5个以内。

    (4)InnoDB表一般都建议有主键列,尤其在高可用集群方案中是作为必须项的。

    (5)建立复合索引时,优先将选择性高的字段放在前面。

    (6)UPDATE、DELETE语句需要根据WHERE条件添加索引。

    (7)不建议使用%前缀模糊查询,例如LIKE “%weibo”,无法用到索引,会导致全表扫描。

    (8)合理利用覆盖索引,例如:

    (9)SELECT email,uid FROM user_email WHERE uid=xx,如果uid不是主键,可以创建覆盖索引idx_uid_email(uid,email)来提高查询效率。

    (10)避免在索引字段上使用函数,否则会导致查询时索引失效。

    (11)确认索引是否需要变更时要联系DBA。

    5)应用规范

    (1)避免使用存储过程、触发器、自定义函数等,容易将业务逻辑和DB耦合在一起,后期做分布式方案时会成为瓶颈。

    (2)考虑使用UNION ALL,减少使用UNION,因为UNION ALL不去重,而少了排序操作,速度相对比UNION要快,如果没有去重的需求,优先使用UNION ALL。

    (3)考虑使用limit N,少用limit M,N,特别是大表或M比较大的时候。

    (4)减少或避免排序,如:group by语句中如果不需要排序,可以增加order by null。

    (5)统计表中记录数时使用COUNT(*),而不是COUNT(primary_key)和COUNT(1);InnoDB表避免使用COUNT(*)操作,计数统计实时要求较强可以使用Memcache或者Redis,非实时统计可以使用单独统计表,定时更新。

    (6)做字段变更操作(modify column/change column)的时候必须加上原有的注释属性,否则修改后,注释会丢失。

    (7)使用prepared statement可以提高性能并且避免SQL注入。

    (8)SQL语句中IN包含的值不应过多。

    (9)UPDATE、DELETE语句一定要有明确的WHERE条件。

    (10)WHERE条件中的字段值需要符合该字段的数据类型,避免MySQL进行隐式类型转化。

    (11)SELECT、INSERT语句必须显式的指明字段名称,禁止使用SELECT * 或是INSERT INTO table_name values()。

    (12)INSERT语句使用batch提交(INSERT INTO table_name VALUES(),(),()……),values的个数不应过多。

    优化设计方案2:业务层优化

    业务层优化应该是收益最高的优化方式了,而且对于业务层完全可见,主要有业务拆分,数据拆分和两类常见的优化场景(读多写少,读少写多)

    640?wx_fmt=png

    1)业务拆分

    ü 将混合业务拆分为独立业务

    ü 将状态和历史数据分离

    业务拆分其实是把一个混合的业务剥离成为更加清晰的独立业务,这样业务1,业务2。。。独立的业务使得业务总量依旧很大,但是每个部分都是相对独立的,可靠性依然有保证。

    对于状态和历史数据分离,我可以举一个例子来说明。

    例如:我们有一张表Account,假设用户余额为100。

    640?wx_fmt=png

    我们需要在发生数据变更后,能够追溯数据变更的历史信息,如果对账户更新状态数据,增加100的余额,这样余额为200。

    这个过程可能对应一条update语句,一条insert语句。

    对此我们可以改造为两个不同的数据源,account和account_hist

    在account_hist中就会是两条insert记录,如下:

    640?wx_fmt=png

    而在account中则是一条update语句,如下:

    640?wx_fmt=png

    这也是一种很基础的冷热分离,可以大大减少维护的复杂度,提高业务响应效率。

    2)数据拆分

    2.1 按照日期拆分,这种使用方式比较普遍,尤其是按照日期维度的拆分,其实在程序层面的改动很小,但是扩展性方面的收益很大。

    • 数据按照日期维度拆分,如test_20191021

    • 数据按照周月为维度拆分,如test_201910

    • 数据按照季度,年维度拆分,如test_2019

    2.2 采用分区模式,分区模式也是常见的使用方式,采用hash,range等方式会多一些,在MySQL中我是不大建议使用分区表的使用方式,因为随着存储容量的增长,数据虽然做了垂直拆分,但是归根结底,数据其实难以实现水平扩展,在MySQL中是有更好的扩展方式。

    2.3 读多写少优化场景

    采用缓存,采用Redis技术,将读请求打在缓存层面,这样可以大大降低MySQL层面的热点数据查询压力。

    2.4 读少写多优化场景,可以采用三步走:

    1) 采用异步提交模式,异步对于应用层来说最直观的就是性能的提升,产生最少的同步等待。

    2) 使用队列技术,大量的写请求可以通过队列的方式来进行扩展,实现批量的数据写入。

    3) 降低写入频率,这个比较难理解,我举个例子

    对于业务数据,比如积分类,相比于金额来说业务优先级略低的场景,如果数据的更新过于频繁,可以适度调整数据更新的范围(比如从原来的每分钟调整为10分钟)来减少更新的频率。

    例如:更新状态数据,积分为200,如下图所示

    640?wx_fmt=png

    可以改造为,如下图所示。

    640?wx_fmt=png

    如果业务数据在短时间内更新过于频繁,比如1分钟更新100次,积分从100到10000,则可以根据时间频率批量提交。

    例如:更新状态数据,积分为100,如下图所示。

    640?wx_fmt=png

    无需生成100个事务(200条SQL语句)可以改造为2条SQL语句,如下图所示。

    640?wx_fmt=png

    对于业务指标,比如更新频率细节信息,可以根据具体业务场景来讨论决定。

    优化设计方案3:架构层优化

    架构层优化其实就是我们认为的那种技术含量很高的工作,我们需要根据业务场景在架构层面引入一些新的花样来。

    640?wx_fmt=png

    3.1.系统水平扩展场景

    3.1.1采用中间件技术,可以实现数据路由,水平扩展,常见的中间件有MyCAT,ShardingSphere,ProxySQL等

    640?wx_fmt=jpeg

    3.1.2 采用读写分离技术,这是针对读需求的扩展,更侧重于状态表,在允许一定延迟的情况下,可以采用多副本的模式实现读需求的水平扩展,也可以采用中间件来实现,如MyCAT,ProxySQL,MaxScale,MySQL Router等

    640?wx_fmt=png

    3.1.3 采用负载均衡技术,常见的有LVS技术或者基于域名服务的Consul技术等

    3.2.兼顾OLTP+OLAP的业务场景,可以采用NewSQL,优先兼容MySQL协议的HTAP技术栈,如TiDB

    3.3.离线统计的业务场景,有几类方案可供选择。

    3.3.1 采用NoSQL体系,主要有两类,一类是适合兼容MySQL协议的数据仓库体系,常见的有Infobright或者ColumnStore,另外一类是基于列式存储,属于异构方向,如HBase技术

    3.3.2 采用数仓体系,基于MPP架构,如使用Greenplum统计,如T+1统计

    优化设计方案4:数据库优化

    数据库优化,其实可打的牌也不少,但是相对来说空间没有那么大了,我们来逐个说一下。

    640?wx_fmt=png

    4.1 事务优化

    根据业务场景选择事务模型,是否是强事务依赖

    对于事务降维策略,我们来举出几个小例子来。

    4.1.1 降维策略1:存储过程调用转换为透明的SQL调用

    对于新业务而言,使用存储过程显然不是一个好主意,MySQL的存储过程和其他商业数据库相比,功能和性能都有待验证,而且在目前轻量化的业务处理中,存储过程的处理方式太“重”了。

    有些应用架构看起来是按照分布式部署的,但在数据库层的调用方式是基于存储过程,因为存储过程封装了大量的逻辑,难以调试,而且移植性不高,这样业务逻辑和性能压力都在数据库层面了,使得数据库层很容易成为瓶颈,而且难以实现真正的分布式。

    所以有一个明确的改进方向就是对于存储过程的改造,把它改造为SQL调用的方式,可以极大地提高业务的处理效率,在数据库的接口调用上足够简单而且清晰可控。

    4.1.2 降维策略2:DDL操作转换为DML操作

    有些业务经常会有一种紧急需求,总是需要给一个表添加字段,搞得DBA和业务同学都挺累,可以想象一个表有上百个字段,而且基本都是name1,name2……name100,这种设计本身就是有问题的,更不用考虑性能了。究其原因,是因为业务的需求动态变化,比如一个游戏装备有20个属性,可能过了一个月之后就增加到了40个属性,这样一来,所有的装备都有40个属性,不管用没用到,而且这种方式也存在诸多的冗余。

    我们在设计规范里面也提到了一些设计的基本要素,在这些基础上需要补充的是,保持有限的字段,如果要实现这些功能的扩展,其实完全可以通过配置化的方式来实现,比如把一些动态添加的字段转换为一些配置信息。配置信息可以通过DML的方式进行修改和补充,对于数据入口也可以更加动态、易扩展。

    4.1.3 降维策略3:Delete操作转换为高效操作

    有些业务需要定期来清理一些周期性数据,比如表里的数据只保留一个月,那么超出时间范围的数据就要清理掉了,而如果表的量级比较大的情况下,这种Delete操作的代价实在太高,我们可以有两类解决方案来把Delete操作转换为更为高效的方式。 

    第一种是根据业务建立周期表,比如按照月表、周表、日表等维度来设计,这样数据的清理就是一个相对可控而且高效的方式了。 

    第二种方案是使用MySQL rename的操作方式,比如一张2千万的大表要清理99%的数据,那么需要保留的1%的数据我们可以很快根据条件过滤补录,实现“移形换位”。

    4.2 SQL优化

    其实相对来说需要的极简的设计,很多点都在规范设计里面了,如果遵守规范,八九不离十的问题都会杜绝掉,在此补充几点:

    4.2.1 SQL语句简化,简化是SQL优化的一大利器,因为简单,所以优越。

    4.2.2 尽可能避免或者杜绝多表复杂关联,大表关联是大表处理的噩梦,一旦打开了这个口子,越来越多的需求需要关联,性能优化就没有回头路了,更何况大表关联是MySQL的弱项,尽管Hash Join才推出,不要像掌握了绝对大杀器一样,在商业数据库中早就存在,问题照样层出不穷。

    4.2.3 SQL中尽可能避免反连接,避免半连接,这是优化器做得薄弱的一方面,什么是反连接,半连接?其实比较好理解,举个例子,not in ,not exists就是反连接,in,exists就是半连接,在千万级大表中出现这种问题,性能是几个数量级的差异。 

    4.3 索引优化

    应该是大表优化中需要把握的一个度。

    4.3.1 首先必须有主键,规范设计中第一条就是,此处不接收反驳。

    4.3.2 其次,SQL查询基于索引或者唯一性索引,使得查询模型尽可能简单。

    4.3.3 最后,尽可能杜绝范围数据的查询,范围扫描在千万级大表情况下还是尽可能减少。

    优化设计方案4:管理优化

    这部分应该是在所有的解决方案中最容易被忽视的部分了,我放在最后,在此也向运维同事致敬,总是为很多认为本应该正常的问题尽职尽责(背锅)。

    640?wx_fmt=png

    千万级大表的数据清理一般来说是比较耗时的,在此建议在设计中需要完善冷热数据分离的策略,可能听起来比较拗口,我来举一个例子,把大表的Drop 操作转换为可逆的DDL操作。

    Drop操作是默认提交的,而且是不可逆的,在数据库操作中都是跑路的代名词,MySQL层面目前没有相应的Drop操作恢复功能,除非通过备份来恢复,但是我们可以考虑将Drop操作转换为一种可逆的DDL操作。

    MySQL中默认每个表有一个对应的ibd文件,其实可以把Drop操作转换为一个rename操作,即把文件从testdb迁移到testdb_arch下面;从权限上来说,testdb_arch是业务不可见的,rename操作可以平滑的实现这个删除功能,如果在一定时间后确认可以清理,则数据清理对于已有的业务流程是不可见的,如下图所示。

    640?wx_fmt=png

    此外,还有两个额外建议,一个是对于大表变更,尽可能考虑低峰时段的在线变更,比如使用pt-osc工具或者是维护时段的变更,就不再赘述了。

    最后总结一下,其实就是一句话:

    千万级大表的优化是根据业务场景,以成本为代价进行优化的,绝对不是孤立的一个层面的优化。

    近期热文:

    个人新书 《MySQL DBA工作笔记》

    个人公众号:jianrong-notes

    QQ群号:763628645

    QQ群二维码如下,个人微信号:jeanron100, 添加请注明:姓名+地区+职位,否则不予通过

    640?wx_fmt=png640?wx_fmt=png

    在看,让更多人看到

    展开全文
  • =''"> age=#{age}, if>, modified = sysdate() where user_id = #{userId} update> age是个int类型的数据,我们在age判断的时候就增加上了判断age!='',这个是存在问题的。 正确的示例 ✅ ...

    前言

    最近项目内更新数据时,发现数字类型字段设置为0时不能正常的更新进数据库,我们打印了下mybatis的sql日志发现字段为0的sql没有被拼接。

    样例

    下面的是错误示例 ❌

      <update id="update" parameterType="com.chengfengfeng.test.domain.People">
            update people
            set
            <if test="age!=null and age !=''">
                age=#{age},
            </if>,
            modified = sysdate()
            where user_id = #{userId}
      </update>
    

    age是个int类型的数据,我们在写age判断的时候就增加上了判断age!='',这个是存在问题的。
    正确的示例 ✅

      <update id="update" parameterType="com.chengfengfeng.test.domain.People">
            update people
            set
            <if test="age!=null">
                age=#{age},
            </if>,
            modified = sysdate()
            where user_id = #{userId}
      </update>
    

    原因是什么呢

    跟踪了下代码,发现在解析xml时数字类型会走下面的判断

    public abstract class OgnlOps implements NumericTypes {
    	//省略其他无用代码
        public static double doubleValue(Object value) throws NumberFormatException {
            if (value == null) {
                return 0.0D;
            } else {
                Class c = value.getClass();
                if (c.getSuperclass() == Number.class) {
                    return ((Number)value).doubleValue();
                } else if (c == Boolean.class) {
                    return (Boolean)value ? 1.0D : 0.0D;
                } else if (c == Character.class) {
                    return (double)(Character)value;
                } else {
                    String s = stringValue(value, true);
                    //这个位置会把’‘空串处理成0
                    return s.length() == 0 ? 0.0D : Double.parseDouble(s);
                }
            }
        }
    }
    

    我们看上面最后一段代码,会把''转换成0.0D,那么我们的最开始的if判断就变成了age != 0,所以当我们把age设置为0时,结果就变成了false,导致sql不会进行拼接,更新数据失败。

    展开全文
  • 其中最重要的就是这条:不要相信一个程序员在加班时间出来的代码。 (软件工程的学说表明,连正常时间好好的代码,也不要太相信。不过这不是本文的重点,略过不提。) (不懂代码的人,看到本文中的Java代码可以...

    作为一个最底层的程序员,我先记录一些只有底层程序员才会知道的事情。如果多年后,我违背自己进入这个行业的初心,走上管理岗位,也能回想起一些禁忌,避免一些错误。

    其中最重要的就是这条:不要相信一个程序员在加班时间写出来的代码

    (软件工程的学说表明,连正常时间好好写的代码,也不要太相信。不过这不是本文的重点,略过不提。)

    (不懂代码的人,看到本文中的Java代码可以略过,不影响理解。)

    一、创造力的时限

    写代码,与写文章、绘画、思考复杂问题,并没有本质上的区别,都是创造性的活动。

    每个人的创造力,都会随着身体状态而波动。广为人知的是,一个人年老体衰后,相比年富力强时,创造力会急剧下降。其实,人每天的状态起伏,也同样会剧烈影响这一点。

    如果是拧螺丝,那么在精疲力尽、拧不动以前,身体状态对结果不会产生太大影响。因为拧螺丝的指标非常简单——拧紧,要做的事也非常机械化——拧,直到它紧,换下一个。

    但如果是写代码,有些事,是不能在状态不好的时候完成的

    比如,在Java里,遍历一个外部的List,做一些处理。如果状态不佳、做事前想的东西少了点,那么很可能直接这么做:

    public void handleAList(List<Integer> aList) {
        for (int i = 0; i < aList.size(); ++i) {
            // Do sth with List#get(int)
        }
    }
    

    这样做是从C/C++带来的一种很直观的做法。有什么问题吗?

    假如外面传入的aList是一个ArrayList,那么List.get(int)的时间复杂度是O(1),算上外面那重循环则是O(n);而假如aList是一个LinkedList,那么List.get(int)的时间复杂度是O(n),算上外面那重循环则是O(n2)!

    (为不懂算法时间复杂度评估的人解释下:在这个场景下,O(n)代表最优、最快,而O(n2)代表不可接受地慢。)

    如果时间充分,那么可以去查看handleAList()的调用位置,看看它传递的是哪种List;而如果思考得够充分,考虑到这两种情况都有可能,那么代码就会做兼容处理,改成这样:

    public void handleAList(List<Integer> aList) {
        for (int i : aList) {
            // Do sth with i
        }
    }
    

    这使用了for-each语法,实际上是用Iterator来做遍历,无论对哪种List都是总共是O(n)的开销。

    注意,这通常不被看做一个bug,普通的黑盒与白盒测试都是无法发现的。只是你的App会比较卡,或者后台会比较慢。当需要解决这种性能问题时,可能需要非常经验丰富的程序员,在海量代码里找数周时间。

    而这一切,在开发之初,只要那个程序员状态好一点,就可以避免。

    一个人,每天的创造力是有时限的。在时限外,他不再是一个优秀的创造者,而是一个笨蛋。

    (为了便于理解,这个例子非常简单,以至于不够贴切。对Java来说,优先使用for-each或Iterator来遍历,已经是一个共识,是技术素养的一部分。)

    二、失误率的飙升

    程序员在写代码的过程中,每天做得最多的应该就是等价变换。

    if (isSthTrue()) {
        // Take some actions.
    }
    

    变换成

    if (!isSthTrue()) 
        return;
    // Take some actions.
    

    这只是最简单的一种逻辑反转,实际上还有更多、更复杂的形式。通过这类变化,对代码做出调整后,程序员可以把代码变得更好,或者做到以前不能做的事。

    而在加班时间、大脑不那么清醒的情况下,很可能会写成这样:

    if (isSthTrue())
        return;
    // Take some actions.
    

    区别仅仅只是少了一个符号,而意义则完全走样。

    这个例子比较简单,出错后也很容易在调试过程中发现、纠正。但是,请不要怀疑,的确会有程序员为了这么个简单的问题,调试整整一个晚上!

    (、!、i,(字体未配置好时)本就难以区分,眼睛疲劳昏花时,在数百个字符里扫来扫去,难以分辨(!i中是否少了个符号,也并不奇怪。而如果换成第二天早晨,很可能只需要瞥一眼。

    大多数管理者,往往会对熬夜的程序员给出一些肯定,并且允许第二天可以休息一天(有些甚至只给一早上)。但如果他们知道内情,会发现自己其实亏了一天。如果程序员正常下班,第二天花一小时解决这个问题,剩下的七个小时可以继续开发。

    还有很多比这复杂得多的变换,或其它类型的代码改动,即使在大脑清醒的情况下也需要花费一些时间,认真思考、小心调试。而如果来了一个问题,你说“必须要今天下班前搞定”,那么程序员会很烦躁,并且越来越烦躁。

    三、烦躁的后果

    一件需要冷静思考、谋定后动的事,如果逼迫人们在烦躁的情况下去做,那么往往会得到意想不到的糟糕结果。

    我有一位前同事,技术实力且不论,心性也不太稳(实际上,像我这种少年老成、未老先衰、找不到妹子都不急的青年,还真不多)。他是一个可以解决问题的人,但是在烦躁的情况下,也经常做出令我瞠目结舌的事。

    比如,有一天,项目组要求某个bug必须解决。他搞到晚上9点还没搞定,找我帮忙。我当时水平也很差,不然也不会那时还在加班,没能帮他解决,只是因此而知道这件事。他后来在10点半时采用了一个规避方案,然后下班了事。

    具体一点是这样的:在一个class中,有多个地方调用同一个Method。其它地方没有问题,唯独某个位置的结果不正确。他改成这样:

    private boolean isSthTrue(int sth) {
        // Implementation A
    }
    
    private boolean isSth1True() {
        // Implementation B
    }
    
    private boolean isSth2True() {
        // Implementation C
    }
    

    本来isSthTrue()是可以做通用判断的,他没有在规定时间内找到根本原因(Root Cause),实际上当时他也根本没有往发现根本原因的方向去查找代码,而是一晚上都在做一些无效的调试。最后没办法调试出好的结果,于是给出问题的地方一个特殊处理——新增了isSth1True()和isSth2True()去那个出错的地方顶替。结果,那个bug的确是解决了,但是后来带出来了另外一个bug。

    不过他也达到了目的,当天下班了。

    而后来,我在代码里发现了另外一组更早就有的接口:

    private boolean isTrueSth1() {
        // Implemented like B
    }
    
    private boolean isTrueSth2() {
        // Implemented like C
    }
    

    我问了一下这两个Method的作者(另一位同事),他根本没有看到有isSthTrue()。

    这件事的最终结果是,解决了一个bug,后来又引起了多个bug,连我也跟着一起焦头烂额

    在这里插入图片描述
    借着这个例子,回头再说一下创造力的时限。

    这位同事,之所以不去找Root Cause,是因为项目组的催逼和自身的烦躁,他平时是可以解决问题的。但是为什么一个简单问题会这么难解决,为什么代码里之前就有一套他要的Method,他却新写一个?

    外部代码环境就不说了,这个class共有2000行。2000行可能并不是特别直观的数目,既不能说多,也不能说少,取决于这个class干什么事。

    后来,另一个比较老道的同事,重构(refactor)了这个class,只用了不到500行——这就说明了一个问题,这个class之前就太过冗余。

    约半年后,我水平也提高了些,总体的项目时间也松散了些,我花了六周重写(rewrite)了这个不大的代码库。这个class最终只用了100行,部分功能都独立封装到了其它class中。

    如果之前,在这个代码库写就之初,就能有一个充分的时间做一个好的架构设计,不需要rewrite就可以只有100行;而如果时间不太充分,却能给应有的时间好好写,也起码能有refactor后的水平,也就是500行。无论是100行,还是500行,后面出的一大堆问题,都不会出现,或者更容易解决。

    这个代码库是怎么来的?

    当初某领导,交给了一个比较厉害的同事,只给一周时间。这位同事加班加点,一周当成两周用,从别的代码里剥离、拼凑出来了一个编译能通过的东西——这就是交给我们维护的代码库。

    四、来自项目最底层的复仇

    前面说的,无论是写出隐蔽的bug,还是解决一个带出俩,其实都是这类事情的阳光面。你没看错,这是阳光的一面。

    还有我不想多说的阴暗面。

    前面说的事情,没有一类是故意的。无论出事的原因是程序员的技术素养不足、加班情况下大失水准、还是原先的代码就非常容易诱导失误,都是程序员在认真努力的情况下,不可自控地犯错。

    还有一类是故意的。

    比如,前几年(2015)携程那小哥儿,就是怒删数据库。当然,他不是为了加班严重而如何如何,而是心爱的运营妹子被公司某高层给……(另有一说,虽然有什么内部的QQ、微信截图,但这仍然是谣言,实际上是黑客攻击。)

    什么程度的压迫,就会得到什么程度的反抗。

    要知道,即使是很努力地去做,也仍然可以出各种问题。而如果要故意捣乱,很多手段,虽然不会引起老板的注意,甚至可以不被认真的代码审查者(reviewer)警觉,但是会客观地影响产品的品质,让用户讨厌一个产品,或者让一个爆款产品最终失败。

    反正埋了雷,领了工资,跳下一家便是——要么给股票、期权,要么充分洗脑,或至少给出足够的加班费(几年后的医疗费),否则就是这个后果。

    我只能说,就我个人而言,最多辞职,不会故意乱搞。这关乎职业道德,关乎我是否意念通达、心境澄明。(坐等穿越去修真:P)

    但是,我不能用自己的道德准绳去要求别人,对吧?

    而且,永远不要指望一个人在承受不道德的对待时,仍然能谨守原来的道德。

    五、结语

    作为一个软件项目的领导者,你在要求某个程序员加班时,其实就已经在冒险;而如果你经常这么干,不要奇怪为什么项目总是延期,或者一到关键时候,总有突发事件。

    只要试验次数够多,可能性再小的事也会发生;而只要试验次数更多,小概率事件也会连续发生。

    所以,最理智、客观的观念就是:欲速则不达,不要相信一个程序员在加班时间写的代码。

    关注微信公众号【秃头哥编程】,领取编程大礼包。

    在这里插入图片描述

    展开全文
  • 要求:把汉语句子【“处罚结果”、“违法依据”、“处罚依据”的】中的汉字(大小)数字金额转为阿拉伯数字, 如: 罚款2000元,罚款俩千元整,罚款人民币两千元; 步骤: 先识别出文字中的数字(中文/其他格式...

     要求:把汉语句子【“处罚结果”、“违法依据”、“处罚依据”的】中的汉字(大小写)数字金额转为阿拉伯数字,

    如:   罚款2000元,罚款俩千元整,罚款人民币两千元;

    步骤:    先识别出文字中的数字(中文/其他格式),  再统一为阿拉伯数字表示(尤其是罚款,便于后续的金额比较);
    # 注意:不能识别“百分之”

    
    #把汉语句子中的汉字(大小写)数字转为阿拉伯数字,不能识别“百分之”
    
    common_used_numerals_tmp = {'零': 0, '一': 1, '二': 2, '两': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10,
                                u'〇': 0, u'壹': 1, u'贰': 2, u'叁': 3, u'肆': 4, u'伍': 5, u'陆': 6, u'柒': 7, u'捌': 8, u'玖': 9,'拾': 10,
                               '百': 100, '千': 1000,u'貮': 2, u'俩': 2, '佰': 100,  '仟': 1000, '萬': 10000, '万': 10000, '亿': 100000000,
                                '億': 100000000, '兆': 1000000000000 }
    
    common_used_numerals = {}
    for key in common_used_numerals_tmp:
        common_used_numerals[key] = common_used_numerals_tmp[key]
    
    def chinese2digits(uchars_chinese):
        total = 0
        r = 1  # 表示单位:个十百千...
        for i in range(len(uchars_chinese) - 1, -1, -1):
            val = common_used_numerals.get(uchars_chinese[i])
            if val >= 10 and i == 0:  # 应对 十三 十四 十*之类
                if val > r:
                    r = val
                    total = total + val
                else:
                    r = r * val
                    # total =total + r * x
            elif val >= 10:
                if val > r:
                    r = val
                else:
                    r = r * val
            else:
                total = total + r * val
        return total
    
    num_str_start_symbol = ['一', '二', '两', '三', '四', '五', '六', '七', '八', '九', '十',
                            '壹' ,'贰', '叁', '肆', '伍' ,'陆','柒' , '捌' ,'玖','拾', '貮', '俩', ]
    more_num_str_symbol = ['零', '一', '二', '两', '三', '四', '五', '六', '七', '八', '九', '十', '百', '千', '万', '亿',
                            '〇', '壹' ,'贰', '叁', '肆', '伍' ,'陆','柒' , '捌' ,'玖','拾', '貮', '俩', '佰', '仟', '萬', '億', '兆']
    
    def ChineseNumToArab(oriStr):
        lenStr = len(oriStr);
        aProStr = ''
        if lenStr == 0:
            return aProStr;
        hasNumStart = False;
        numberStr = ''
        for idx in range(lenStr):
            if oriStr[idx] in num_str_start_symbol:
                if not hasNumStart:
                    hasNumStart = True;
                numberStr += oriStr[idx]
            else:
                if hasNumStart:
                    if oriStr[idx] in more_num_str_symbol:
                        numberStr += oriStr[idx]
                        continue
                    else:
                        numResult = str(chinese2digits(numberStr))
                        numberStr = ''
                        hasNumStart = False;
                        aProStr += numResult
                aProStr += oriStr[idx]
                pass
        if len(numberStr) > 0:
            resultNum = chinese2digits(numberStr)
            aProStr += str(resultNum)
        return aProStr
    
    if __name__ == '__main__':
    
        testStr = ['罚款2000元','罚款两千元','罚款俩千元整','罚款人民币两千元',
                   '《中华人民共和国水污染防治法》第八十三条第(二)项,并处十万元以上一百万元以下的罚款',
                   '十一','一百二十三','两百三十二','一千二百零三','一万一千一百零一',u'十万零三千六百零九',u'一百二十三万四千五百六十七',
                   u'一千一百二十三万四千五百六十七', u'一亿一千一百二十三万四千五百六十七', u'一百零二亿五千零一万零一千零三十八',
                   u'一千一百一十一亿一千一百二十三万四千五百六十七', u'一兆一千一百一十一亿一千一百二十三万四千五百六十七',
                   '我有百三十二块钱', '十二个套餐', '一亿零八万零三百二十三', '今天天气真不错',
                   '百分之八十 discount rate很高了', '千万不要','我们俩个人', '这个invoice value值一百万',
                   '我的一百件商品have quality','找一找我的收藏夹里,有没有一个眼镜' ]
        for tstr in testStr:
            print(tstr + ' = ' + ChineseNumToArab(tstr))
    

     

    展开全文
  • 以我们学习“机器学习”的经验来看,很多高大上的概念刚开始不懂也没关系,先个东西来跑跑,有个感觉了之后再学习那些概念和理论就快多了。如果别人已经做好了轮子,直接拿过来用则更快。因此, 本文直接用 ...
  • 如何设计或优化千万级别的大表?此外无其他信息,个人觉得这个话题有点范,就只好简单说下该如何做,对于一个存储设计,必须考虑业务特点,收集的信息如下: 1.数据的容量:1-3年内会大概多少条数据,每条数据大概...
  • 如何设计或优化千万级别的大表?此外无其他信息,个人觉得这个话题有点范,就只好简单说下该如何做,对于一个存储设计,必须考虑业务特点,收集的信息如下: 1.数据的容量:1-3年内会大概有多少条数据,每条数据...
  • 可在写作时实现千万次的实时检索匹配,推荐后文内容。让写作像代码一样可以“自动补全”。【2021年注:这个是几年前着玩的项目,现在已经不维护了。】 灵感来源 之前做了查重系统(参见项目: ),获得了...
  • (全程实战干货,建议收藏)》 《千万不要轻易尝试“熊猫烧香”,这不,我后悔了!》 《清明节偷偷训练“熊猫烧香”,结果我的电脑为熊猫“献身了”!》 《7.3万肝爆Java8新特性,我不信你能看完!(建议收藏)》 ...
  • 官方也说了会在后续的版本持续进行翻译,并且使得翻译过来的词汇更符合我们的认知,争取做到见知意。 一,安装汉化Unity 1.1 Hub安装: 打开Unity Hub,选择安装任意版本,将右侧滑动条拉到底部,勾选 ☑️ 简体...
  • 20行Python代码爬取王者荣耀全英雄皮肤

    万次阅读 多人点赞 2019-11-21 22:01:03
    引言 王者荣耀大家都玩过吧,没玩...爬虫是非常有趣的,因为它非常直观,视觉冲击感强,出来也很有成就感,爬虫虽然强大,但千万不能随意爬取隐私信息。 最后,如果对文中程序有更好的建议,欢迎评论区留言。
  • MySQL千万级数据量优化方案

    千次阅读 2020-02-19 21:52:30
    前言 ...千万级大表如何优化,这是一个很有技术含量的问题,通常我们的直觉思维都会跳转到拆分或者数据分区。除此之外,还有其他的思路和解决方案。根据本人多年的工作经验,做了如下总结。 方案 ...
  • 浮点数判等千万不要成 a == b
  • 一手好:硬笔书法轻松自学指南(知乎周刊 Plus) 知乎编辑团队 楷书,认知好的范本 2017-03-16 《黄自元间架结构九十二法》 选本好字帖 2017-03-16 先谈书体。 前人对练习书法的程序,各有主张。有的认为...
  • 记录使用JAVA程序处理千万级的数据表 要求:原表有4000w+数据,需要对其中message字段进行数据处理,并将处理的结果写入result字段中 优化:分表,sql的优化 过程: 最开始是啥都不懂,打算一次性将4000w条数据获取...
  • 应用系统操作数据集分为读多少和读多多两种,业务场景分别是什么?这节我们介绍下。 读多少的业务场景 普遍来说,绝大多数系统都是读多多读少的业务场景 打车、导航等业务场景为读多多读...
  • OCR(联机手写汉字识别)

    万次阅读 2005-09-01 14:57:00
    2 联机手写汉字识别——笔输入——笔输入2.1 什么是笔输入——从键盘输入说起笔(式)输入也叫做手写输入。它的用途是在一块跟计算机连接的书写板上写字,即时把字符输入计算机。必须指出的是,笔输入的作用不只是...
  •  王珞丹多年来一直坚持在微博上手写人生感悟,与她的千万粉丝和网友们互动“见如唔”。 随着文化读书类节目的热播,关于读书、手写书信等多年被大家遗忘的文化传统又回归大众视野。其实在热闹的娱乐圈,能静...
  • 用JAVA数字金字塔

    万次阅读 多人点赞 2018-07-11 10:04:20
    总结了一下,代码其实就是思路有了,那么代码就会迎刃而解,没有思想的撸代码就是流氓。 不管是用什么编程语言,拿到题目先要分析思路,找出最快的解决方法,那么解题就会很快的了,千万不要拿到题边撸代码边想...
  • 没错,就是——注释、文档、别人不注释和别人不文档。 因此,在《流浪地球》刷屏之后,仿其而出的“代码千万行,注释第一行;编程不规范,同事两行泪”(来自CSDN公众号)就在技术圈传播起来,由此可见,...
  • 的话,千万不要把它放在第一个,如果一定要放在第一个的话,得使用转义符。 -  在 [ ] 表示字符的范围,比如 [a - z] 表示 a 与 z 之间的  26  个字母, [a - zA - Z] 这个表示 a - z 和 A ...
  • 繁体大全

    2012-02-26 16:14:46
    比较全面的繁体字符表,可用于程序或查阅繁体字库
  • 输入一个数字,输出它的大写汉字。 就像这样: 这算是一个有趣的题目了。但是早前我Java基础特别差就没能实现它。 当然,这次我也是在以前的垃圾代码上做出的修改。 代码整体看起来会很乱。甚至有一些bug。 ...
  • 工科论文怎么

    千次阅读 2020-08-04 07:43:06
    论文的时候,感觉有点无从下手,查了点资料,根据知乎度娘上查的资料稍微总结了一下,在这里记录一下,仅为本人学习所用 文章目录论文写作准备搜集论文工科论文工科论文分类:论文基本框架初稿怎么注意写作步骤...
  • 事情是这样的,前几天我在刷B站的时候看到一个大佬用训练了一个自动高考作文的AI 链接: https://www.bilibili.com/video/BV1pr4y1w7uM?from=search&seid=3451527309155759796 那我就想既然别人能训练自动...
  • 千万级别的数据库优化

    千次阅读 2018-07-09 10:35:54
    转载:https://iamjohnnyzhuang.github.io/database/2016/07/05/%E5%8D%83%E4%B8%87%E7%BA%A7%E6%95%B0%E6%8D%AE%E4%B8%8B%E7%9A%84Mysql%E4%BC%98%E5%8C%96.html前言平时在一些小web系统时,我们总会对mysql...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 124,878
精华内容 49,951
关键字:

千万的字怎么写