六、数据库相关

数据库中的锁是怎么实现的?

悲观并发控制(PCC):心态悲观,假定多用户并发的事物在处理时都会引起并发冲突,每次操作数据的时候都会上锁
先取锁再访问的策略,为数据的安全提供了保证,但是加锁会产生额外的开销,增加死锁的机会,只读型事物不会产生冲突也不需要加锁

乐观并发(OCC):心态乐观,假定多用户并发的事物在处理时不会彼此互相影响,只在提交时检查有没有其它事物修改了该数据
可以获得更大的吞吐量,但是发生冲突事物就会回滚重新执行

多版本并发(MVCC):每个写操作都会创建一个新版本的数据,读操作根据可见性规则返回其中一个数据快照
读 - 写冲突不加锁,非阻塞读的同时避免了脏读和不可重复读,但需要管理和挑选数据版本
 

1.数据库事务的几种粒度

    事务是保证每一次操作都是可靠的,即使出现了异常,也不会破坏数据

  • 原子性(Atomicity): 事务要么全部完成,要么全部取消。 如果事务崩溃,状态回到事务之前(事务回滚)。
  • 一致性(Consistency): 只有合法的数据(依照关系约束和函数约束)才能写入数据库。
  • 持久性(Durability): 一旦事务提交,不管发生什么(崩溃或者出错),数据要保存在数据库中。
  • 隔离性(Isolation): 如果2个事务 T1 和 T2 同时运行,事务 T1 和 T2 最终的结果是相同的,不管 T1和T2谁先结束。

2.MySQL InnoDB的特点?

InnoDB特点:支持事物,支持行锁和外键

1)表存储在一个逻辑表空间中,而在MYISAM中,每个表可能被存放到分离的文件中。

2)InnoDB的CPU效率是其他数据库引擎锁不能匹敌的

3)InnoDB不创建目录,会在MYsql数据目录下创建表名可以自动扩展大小的数据文件,还有两个log日志文件

4)适用场景:适用于大多数事物处理过程(在mysql5.7及以后的版本已经支持全文索引和空间函数)

MyISAM特点:基于ISAM存储引擎,较高的插入和查询速度,不支持事物。

1)可以把数据文件和索引文件放在不同的目录

2)表级锁定,数据在更新时锁定整个表。表共读锁:select,表独占锁:update,delete,insert

3)数据读写过程中相互锁定

4)通过key_buffer_size设置缓存索引

5)不支持外键约束,支持全文索引。支持BLOB和TEXT的前500个字符索引

6)建表时会生成三个文件,文件名均以表名开始

表定义:.frm文件

数据文件:.MYD(MYData)

索引文件:.MYI(MYIndex)

7)使用场景:非事务型应用,只读类应用,空间类应用

区别:MYISAM不支持事物,强调的是性能,执行速度快,InnoDB支持事物,外键,行锁等高级数据库功能。

总结:如果插入和查询较多的话,建议使用MYISAM,如果需要事物安全的能力,且需要并发控制,则使用InnoDB

3.乐观锁和悲观锁的区别?

1)乐观锁认为当前操作不会导致冲突,不进行加锁,等到更新数据时,在进行冲突处理。数据库不是自带乐观锁的,需要自己实现。

通常实现有两种方式:

a.使用数据库版本

在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。

举例:下单操作包括3步骤:

1.查询出商品信息

select (status,status,version) from t_goods where id=#{id}

2.根据商品信息生成订单

3.修改商品status为2

update t_goods 

set status=2,version=version+1

where id=#{id} and version=#{version};

b.使用时间戳(timestamp)

在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

2)悲观锁在操作数据时,认为此操作可能会出现数据冲突,所以每次操作时都进行获取锁操作。

悲观锁分为两种:共享锁和排它锁。

共享锁:是读操作创建的锁,其他用户可以并发读取数据,但不能修改数据,直到释放共享锁

SELECT ... LOCK IN SHARE MODE

排它锁 :也称为写锁。若事务1对数据对象A加了排它锁,那么事务1可以读写A,其他事物不能对A加锁或操作,直到事物1释放在A上的锁。

SELECT ... FOR UPDATE

行锁:锁住某一行数据。注意行锁是基于索引的,在没有索引的情况下会锁定整张表。

行锁实现:行锁也是有两种的:共享锁和排他锁,一般在这两个锁的条件下,在查询条件上添加索引就可以了。(例如:添加索引,在sql中添加for update,不然会锁定整个表。)

表锁:锁住整张表。

上一个问题说到InnoDB既可以使用行锁也可以使用表锁,那要怎么区分呢?当使用索引时,才会使用行级锁,否则使用表级锁

4.数据库隔离级别是什么?有什么作用?

隔离就是隔离其他事物,当有多个事物存在时,可能会出现脏读(读取了其他事物未提交的数据),不可重复读(一个事物,相同的查询条件,返回记录是同一条,但是这条数据的某些字段被修改了,强调修改),幻读(一个事物内,两次查询的结果记录数不一致,强调新增)。因此出现了事物隔离级别事。务隔离级别越高,在并发下会产生的问题就越少,但同时付出的性能消耗也将越大,因此很多时候必须在并发性和性能之间做一个权衡。

读未提交,可能出现脏读,不可重复读,幻读

读已提交,解决脏读,可能出现不可重复读,幻读

可重复读,解决脏读,不可重复读,可能出现幻读

序列化,解决脏读,不可重复读,幻读

5.MySQL主备同步的基本原理。

(1)什么是主从同步

当主库(master)数据发生变化时,会实时同步到从库(slave)中

(2)优点

水平扩展数据库的负载均衡能力,提高容错能力,高可用,数据备份

(3)原理

首先我们来了解master-slave的体系结构。
image_1bb0gkf461ram981c3r1fcpvia1g.png-42.4kB

不管是delete、update、insert,还是创建函数、存储过程,所有的操作都在master上。
当master有操作的时候,slave会快速的接收到这些操作,从而做同步。

但是,这个机制是怎么实现的呢?

在master机器上,主从同步事件会被写到特殊的log文件中(binary-log);
在slave机器上,slave读取主从同步事件,并根据读取的事件变化,在slave库上做相应的更改。

如此,就实现了主从同步了!

下面我们来详细的了解。

a.主从同步事件有哪些

  • statement:会将对数据库操作的sql语句写入到binlog中。

  • row:会将每一条数据的变化写入到binlog中。

  • mixed:statement与row的混合。Mysql决定什么时候写statement格式的,什么时候写row格式的binlog。

b.在master机器上的操作

当master上的数据发生改变的时候,该事件(insert、update、delete)变化会按照顺序写入到binlog中。

binlog dump线程

当slave连接到master的时候,master机器会为slave开启binlog dump线程。
当master的binlog发生变化的时候,binlog dump线程会通知slave,并将相应的binlog内容发送给slave。

c.在slave机器上的操作

当主从同步开启的时候,slave上会创建2个线程。

  • I/O线程。该线程连接到master机器,master机器上的binlog dump线程会将binlog的内容发送给该I/O线程。该I/O线程接收到binlog内容后,再将内容写入到本地的relay log。

  • SQL线程。该线程读取I/O线程写入的relay log。并且根据relay log的内容对slave数据库做相应的操作。

d.如何在master、slave上查看上述的线程?

使用SHOW PROCESSLIST命令可以查看。

如图,在master机器上查看binlog dump线程。
image_1bb0nlnmf1g1t18hi1m6colk8rb2h.png-44.7kB

如图,在slave机器上查看I/O、SQL线程。
image_1bb0nraek1mtr1o2r1ivr11cj1jq72u.png-57.6kB

https://segmentfault.com/a/1190000008663001

6.如何从一张表中查出name字段包含“XYZ”的所有行?如何从一张表中查出name字段不包含“XYZ”的所有行

模糊匹配相关内容

SELECT 字段 FROM 表 WHERE 某字段 Like 条件

条件有以下四种

(1)%,匹配任意个字符

比如 SELECT * FROM [user] WHERE u_name LIKE '%三%'

会把u_name 有“三”的记录全找出来

(2)-,匹配一个字符

比如 SELECT * FROM [user] WHERE u_name LIKE '_三_'
只找出“唐三藏”这样u_name为三个字且中间一个字是“三”的;

(3)[ ] :表示括号内所列字符中的一个(类似正则表达式)。

比如 SELECT * FROM [user] WHERE u_name LIKE '[张李王]三'
将找出“张三”、“李三”、“王三”(而不是“张李王三”);

(4)[^ ] :表示不在括号所列之内的单个字符

比如 SELECT * FROM [user] WHERE u_name LIKE '[^张李王]三'
将找出不姓“张”、“李”、“王”的“赵三”、“孙三”等;

SELECT * FROM [user] WHERE u_name LIKE '老[^1-4]';
将排除“老1”到“老4”,寻找“老5”、“老6”、……
上面题目答案:

(1)select*from table where name like'%XYZ%'

(2)select * from table where  name not  like '%XYZ%'

select * from table1 where charindex('关键字' , aa) = 0 

7.索引数据结构(字典+BitTree)

MySQL数据库支持多种索引类型,如BTree索引,哈希索引,全文索引。下面主要了解一下B-tree和B+树的数据结构,然后再了解一下为什么B+树适合作索引。

B-Tree

B-Tree是由平衡二叉树演化来的,平衡二叉树是指节点有序,树的左右层级差不大于1。我们知道二叉树的查找和树的高度有关,但平衡二叉树在数据较多的情况下,二叉树的高度很大,因此出现平衡多叉树,即B-Tree。

性质:

每个节点最多有m个子节点(m>=2)

除根节点外,每个节点拥有的关键字数范围:ceil(m/2)<=x<=m

根节点至少有两个孩子

所有叶子节点均在同一层、叶子节点除了包含了关键字和关键字记录的指针外也有指向其子节点的指针只不过其指针地址都为null对应下图最后一层节点的空格子

如果一个非叶节点有N个子节点,则该节点的关键字数等于N-1;

所有节点关键字是按递增次序排列,并遵循左小右大原则;

根据B-tree的特性,根据key索引功能就可以简单实现。因为每个节点的key按照从小到大的顺序排列,因此当key大于当前节点时,即证明要在当前节点的子节点中查询,当key等于当前节点的关键字时,直接返回结果。下面是伪代码:

BTree_Search(node, key) {
    if(node == null) return null;
    foreach(node.key)
    {
        if(node.key[i] == key) return node.data[i];
        if(node.key[i] > key) return BTree_Search(point[i]->node);
    }
    return BTree_Search(point[i+1]->node);
}
data = BTree_Search(root, my_key);

B+Tree

(1)B+跟B树不同B+树的非叶子节点不保存关键字记录的指针,这样使得B+树每个节点所能保存的关键字大大增加;

(2)B+树叶子节点保存了父节点的所有关键字和关键字记录的指针,每个叶子节点的关键字从小到大链接;也便于做区间查找;

(3)B+树的根节点关键字数量和其子节点个数相等;

(4)B+的非叶子节点只进行数据索引,不会存实际的关键字记录的指针,所有数据地址必须要到叶子节点才能获取到,所以每次数据查询的次数都一样;

以上可以看出B+树读写磁盘代价更低,因为B+树的内部节点没有指向关键字的具体指针(即根据关键字查找到具体数据的指针),内部结构比B-树更小。如果把所有统一内部节点的关键字存储在同一个盘块中,那么盘块能容纳的关键数量更多,一次读入内存中需要查询出来的关键字就越多,这个磁盘IO就越少。因此B+Tree更适合作索引结构。

http://blog.codinglabs.org/articles/theory-of-mysql-index.html

B树和B+树的区别

1、B树,每个节点都存储key和data,所有节点组成这棵树,并且叶子节点指针为nul,叶子结点不包含任何关键字信息。

2、B+树,所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接

所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (而B 树的非终节点也包含需要查找的有效信息)

为什么说B+比B树更适合实际应用中操作系统的文件索引和数据库索引?

1、B+的磁盘读写代价更低。

B+的内部结点并没有指向关键字具体信息的指针,因此其内部结点相对B树更小。

如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。

2、B+-tree的查询效率更加稳定。

由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

8.mysql存储引擎中索引的实现机制;

MyISAM索引实现

MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。下图是MyISAM索引的原理图

MyISAM的索引方式也叫做“非聚集”的,之所以这么称呼是为了与InnoDB的聚集索引区分

InnoDB索引实现

虽然InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同。

第一个重大区别是InnoDB的数据文件本身就是索引文件。从上文知道,MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。

InnoDB主索引(同时也是数据文件)的示意图,可以看到叶节点包含了完整的数据记录。这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。

第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域。例如,图11为定义在Col3上的一个辅助索引:

这里以英文字符的ASCII码作为比较准则。聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。

了解不同存储引擎的索引实现方式对于正确使用和优化索引都非常有帮助,例如知道了InnoDB的索引实现后,就很容易明白为什么不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。再例如,用非单调的字段作为主键在InnoDB中不是个好主意,因为InnoDB数据文件本身是一颗B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。

索引优化:

MySQL的优化主要分为结构优化(Scheme optimization)和查询优化(Query optimization)。索引策略主要属于结构优化范畴。

上面文章详细介绍了很多优化内容,我这里只简单的提炼一下。

最左前缀优化:

(1)当建立了一个组合索引(a,b,c)时,where条件中按照组合索引顺序(a,b,c)的查询可以走索引。而且mysql中对不是组合顺序的查询进行了调整顺序,也可以走索引,例如(c,b,a)

(2)当索引中不包含组合索引的所有列时,走最左匹配原则,例如(a)或者(a,b)查询条件精确匹配索引的左边连续一个或几个列时,可以使用索引;(a,c)中,用到了索引中列的精确匹配,但是中间某个条件未提供,所以只有a可以走索引;(b,c)中没有指定索引的第一列就不可以走索引;

(3)如果通配符%不出现在开头,则可以用到索引

(4)范围列可以用到索引(必须是最左前缀),但是范围列后面的列无法用到索引。同时,索引最多用于一个范围列,因此如果查询条件中有两个范围列则无法全用到索引。

(5)查询条件中含有函数或表达式不走索引

9.SQL什么情况下不会使用索引

不包含,不等于,函数

通配符%

字符集不一致

隐式类型转换

10.一般在什么字段上建索引(过滤数据最多的字段)

索引可以加快查询速度,那么是不是只要是查询语句需要,就建上索引?答案是否定的。因为索引虽然加快了查询速度,但索引也是有代价的:索引文件本身要消耗存储空间,同时索引会加重插入、删除和修改记录时的负担,另外,MySQL在运行时也要消耗资源维护索引,因此索引并不是越多越好。一般两种情况下不建议建索引。

第一种情况是表记录比较少,例如一两千条甚至只有几百条记录的表,没必要建索引,让查询做全表扫描就好了。至于多少条记录才算多,这个个人有个人的看法,我个人的经验是以2000作为分界线,记录数不超过 2000可以考虑不建索引,超过2000条可以酌情考虑索引。

另一种不建议建索引的情况是索引的选择性较低。所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:

Index Selectivity = Cardinality / #T

显然选择性的取值范围为(0, 1],选择性越高的索引价值越大,这是由B+Tree的性质决定的。例如,上文用到的employees.titles表,如果title字段经常被单独查询,是否需要建索引,我们看一下它的选择性:

title的选择性不足0.0001(精确值为0.00001579),所以实在没有什么必要为其单独建索引。

有一种与索引选择性有关的索引优化策略叫做前缀索引,就是用列的前缀代替整个列作为索引key,当前缀长度合适时,可以做到既使得前缀索引的选择性接近全列索引,同时因为索引key变短而减少了索引文件的大小和维护开销

11.SQL优化

一、表的设计
0、必须使用默认的InnoDB存储引擎--支持事务、行级锁、并发性能好、CPU及内存缓存页优化使得资源利用率高
1、表和字段使用中文注释--便于后人理解
2、使用默认utf8mb4字符集--标准、万国码、无乱码风险、无需转码
3、禁止使用触发器、视图、存储过程和event
4、禁止使用外键--外键导致表之间的耦合,update和delete操作都会涉及相关表,影响性能
--架构方向:对数据库性能影响较大的特性少用;应将计算集中在服务层,解放数据库CPU;数据库擅长索引和存储,勿让数据库背负重负
5、禁止存大文件或者照片--在数据库里存储URI
字段:
6、必须把字段定义为NOT NULL并设置默认值--null值需要更多的存储空间;
字段中有null值的话,name != 'san' 查询结果中不包含name is null的记录
7、禁止使用TEXT/BOLB字段类型--浪费磁盘和内存空间,非必要的大量的大字段查询导致内存命中率降低,影响数据库性能
索引:
8、单表索引控制在5个以内
9、单索引不超过5个字段--超过5个以及起不到有效过滤数据的效果
10、建立组合索引,必须把区分度高的字段放在前边--更加有效的过滤数据
11、数据区分度不大的字段不易使用索引--例如:性别只有男,女,订单状态,每次过滤数据很少
二、SQL查询规范:
1、禁止使用select *,只获取需要的字段--查询很多无用字段,增加CPU/IO/NET消耗;不能有效的利用覆盖索引;增删字段易出bug
2、禁止使用属性的隐式转换select * from customer where phone=123123--会导致全表扫描,不能命中索引,要加引号
3、禁止在where条件上使用函数和计算
4、禁止负向查询(NOT != <> !< !> MOT IN NOT LIKE)和%开头的like(前导模糊查询)--会导致全表扫描
5、禁止大表使用JOIN查询和子查询--会产生临时表,消耗较多CPU和内存,影响数据库性能
6、在属性上进行计算不能命中索引--如 select * from order where YEAR(date) <= '2017'不能命中索引导致全表扫描
7、复合索引最左前缀--例如user 表建立了(userid,phone)的联合索引
有如下几种写法:
(1)select * from user where userid = ? and phone = ?
(2)select * from user where phone=? and userid= ?
(3)select * from user where phone = ?
(4)select * from user where userid = ?
其中(1)(2)(4)可以命中索引,(3)会导致全表扫描
8、明知道只有一条记录返回,建议加上limit 1

12.如何优化数据库性能

索引、分库分表、批量操作、分页算法、升级硬盘SSD、业务优化、主从部署

13.Redis,RDB和AOF,如何做高可用、集群

(1)什么是redis

是一种key-value型内存数据库,数据操作是在内存中进行的,定期异步将数据同步到硬盘上。优点:快速和数据持久化功能。一般用于较小数据量的高性能能操作和运算上。缺点:受物理内存的影响,不能对海量数据进行高性能读写。

(2)支持的数据类型

string hash list set hashset

(3)集群方案应该怎么做?有哪些方案?

官方的redis cluster3.0。自带集群,特点在于他的分布式算法不是一致性hash而是hash槽的概念。

hash槽

redis集群有16384个哈希槽,每个key通过crc16(hash算法)校验后,对163841取模决定放置哪个槽,集群每个节点负责一部分hash槽。

(4)怎么保证redis中的数据都是热点数据

redis内存中的数据集大小上升到一定大小时,会执行淘汰策略

no-eviction:禁止淘汰数据,返回错误。(比如当内存到达限制,用户端尝试缓存更多数据时)

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

volatile-lru: 从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

(5)Redis在缓存功能中是怎么实现的?

redis是整个系统的缓存,如果redis挂了,服务器上内存和磁盘上的数据都丢了,那么访问数据时就不能使用缓存,每次从数据库中读取文件,这个代价是很大的。索引要使用Redis的持久化功能,就是将redis中的数据,持久化到磁盘上,再备份到其他地方(阿里云,云服务等)当redis挂了的时候,就可以从磁盘上获取相应的缓存数据。

RDB和AOF两种持久机制的介绍

RDB(redis database:核心函数rdbSave,生成rdb文件,rdbLoad,从文件加载内存)持久化机制,对redis中的数据执行周期性的持久化。将当前进程中的数据生成快照保存到硬盘(因此也称作快照持久化),保存的文件后缀是rdb;当Redis重新启动时,可以读取快照文件恢复数据。

RDB优缺点:优点:rdb文件紧凑,体积小,网络传输快,适合全量复制;恢复速度比AOF快,性能较高。缺点:数据快照持久化的方式决定了做不到实时持久化,数据会大量丢失。rbd文件需要满足特定的格式,兼容性差。


AOF机制,对每条写入命令作为aof日志(是一种以redis通讯协议为格式的命令文本,比rdb简单),以append-only的模式写入一个日志文件中,在redis重启的时候,可以通过回放AOF日志中的写入指令来重新构建整个数据集。与RDB相比,AOF实时性更好

AOF优缺点:秒级持久化,兼容性好,缺点是文件大,恢复慢,性能较差

持久化策略选择

对于RDB持久化,一方面是bgsave在进行fork操作时Redis主进程会阻塞,另一方面,子进程向硬盘写数据也会带来IO压力;对于AOF持久化,向硬盘写数据的频率大大提高(everysec策略下为秒级),IO压力更大,甚至可能造成AOF追加阻塞问题(后面会详细介绍这种阻塞),此外,AOF文件的重写与RDB的bgsave类似,会有fork时的阻塞和子进程的IO压力问题。相对来说,由于AOF向硬盘中写数据的频率更高,因此对Redis主进程性能的影响会更大。

(fork,它的作用是克隆进程,也就是将原先的一个进程再克隆出一个来,克隆出的这个进程就是原进程的子进程,这个子进程和其他的进程没有什么区别,同样拥有自己的独立的地址空间。不同的是子进程是在fork返回之后才开始执行的,就像一把叉子一样,执行fork之后,父子进程就分道扬镳了,所以fork这个名字就很形象,叉子的意思。)

(6)缓存穿透:缓存系统是根据key取缓存查询,如果查找不到对应的value就去后端系统找(如数据库)。一些恶意请求会故意查询不存在的key,请求两很大就会对后端系统造成很大的压力。

如何避免:

a.对查询结果为空的情况也进行缓存,缓存时间设置短一些。

b.对不存在的key进行过滤。可以将所有的key放到一个bitmap钟,查询时,通过此map过滤

(7)缓存雪崩:当缓存服务器重启或者大量缓存在同一时间失效,这样也会给后端造成很大的压力。

如何避免:

a.缓存失效后,通过加锁或者队列控制读数据库,写缓存的线程数量,比如某一条数据只允许一个线程查询数据和写缓存,其他线程等待。

b.二级缓存,A1为原始缓存,A2为拷贝级缓存,A1失效时,可以访问A2。A1缓存失效时间设为短期,A2设置为长期

c.不同的key设置不同的过期时间,让缓存失效的时间尽量均匀

14.如何解决高并发减库存问题

https://blog.csdn.net/caomiao2006/article/details/38568825

先来就库存超卖的问题作描述:一般电子商务网站都会遇到如团购、秒杀、特价之类的活动,而这样的活动有一个共同的特点就是访问量激增、上千甚至上万人抢购一个商品。然而,作为活动商品,库存肯定是很有限的,如何控制库存不让出现超买,以防止造成不必要的损失是众多电子商务网站程序员头疼的问题,这同时也是最基本的问题。

从技术方面剖析,很多人肯定会想到事务,但是事务是控制库存超卖的必要条件,但不是充分必要条件。

举例:

总库存:4个商品

请求人:a、1个商品 b、2个商品 c、3个商品

程序如下:

beginTranse(开启事务)
try{
    $result = $dbca->query('select amount from s_store where postID = 12345');
    if(result->amount > 0){
        //quantity为请求减掉的库存数量
        $dbca->query('update s_store set amount = amount - quantity where postID = 12345');
    }
}catch($e Exception){
    rollBack(回滚)
}
commit(提交事务)

以上代码就是我们平时控制库存写的代码了,大多数人都会这么写,看似问题不大,其实隐藏着巨大的漏洞。数据库的访问其实就是对磁盘文件的访问,数据库中的表其实就是保存在磁盘上的一个个文件,甚至一个文件包含了多张表。例如由于高并发,当前有三个用户a、b、c三个用户进入到了这个事务中,这个时候会产生一个共享锁,所以在select的时候,这三个用户查到的库存数量都是4个,同时还要注意,mysql innodb查到的结果是有版本控制的,再其他用户更新没有commit之前(也就是没有产生新版本之前),当前用户查到的结果依然是就版本;

然后是update,假如这三个用户同时到达update这里,这个时候update更新语句会把并发串行化,也就是给同时到达这里的是三个用户排个序,一个一个执行,并生成排他锁,在当前这个update语句commit之前,其他用户等待执行,commit后,生成新的版本;这样执行完后,库存肯定为负数了。但是根据以上描述,我们修改一下代码就不会出现超买现象了,代码如下:

beginTranse(开启事务)
try{
    //quantity为请求减掉的库存数量
    $dbca->query('update s_store set amount = amount - quantity where postID = 12345');
    $result = $dbca->query('select amount from s_store where postID = 12345');
   //这个result是上一个提交事务中剩余库存,如果库存为0了,那么本次操作就抛出异常,回滚
    if(result->amount < 0){
      throw new Exception('库存不足');
    }
}catch($e Exception){
    rollBack(回滚)
}
commit(提交事务)

另外,更简洁的方法:

beginTranse(开启事务)
try{
    //quantity为请求减掉的库存数量
    $dbca->query('update s_store set amount = amount - quantity where amount>=quantity and postID = 12345');
}catch($e Exception){
    rollBack(回滚)
}
commit(提交事务)

1、在秒杀的情况下,肯定不能如此高频率的去读写数据库,会严重造成性能问题的,必须使用缓存,将需要秒杀的商品放入缓存中,并使用锁来处理其并发情况。当接到用户秒杀提交订单的情况下,先将商品数量递减(加锁/解锁)后再进行其他方面的处理,处理失败在将数据递增1(加锁/解锁),否则表示交易成功。当商品数量递减到0时,表示商品秒杀完毕,拒绝其他用户的请求。
把你要卖出的商品比如10个商品放到缓存中;然后在memcache里设置一个计数器来记录请求数,这个请求数你可以以你要秒杀卖出的商品数为基数,比如你想卖出10个商品,只允许100个请求进来。那当计数器达到100的时候,后面进来的就显示秒杀结束,这样可以减轻你的服务器的压力。然后根据这100个请求,先付款的先得后付款的提示商品以秒杀完。

2、首先,多用户并发修改同一条记录时,肯定是后提交的用户将覆盖掉前者提交的结果了。

这个直接可以使用加锁机制去解决,乐观锁或者悲观锁。
乐观锁,就是在数据库设计一个版本号的字段,每次修改都使其+1,这样在提交时比对提交前的版本号就知道是不是并发提交了,但是有个缺点就是只能是应用中控制,如果有跨应用修改同一条数据乐观锁就没办法了,这个时候可以考虑悲观锁。
悲观锁,就是直接在数据库层面将数据锁死,类似于oralce中使用select xxxxx from xxxx where xx=xx for update,这样其他线程将无法提交数据。
除了加锁的方式也可以使用接收锁定的方式,思路是在数据库中设计一个状态标识位,用户在对数据进行修改前,将状态标识位标识为正在编辑的状态,这样其他用户要编辑此条记录时系统将发现有其他用户正在编辑,则拒绝其编辑的请求,类似于你在操作系统中某文件正在执行,然后你要修改该文件时,系统会提醒你该文件不可编辑或删除。

3、不建议在数据库层面加锁,建议通过服务端的内存锁(锁主键)。当某个用户要修改某个id的数据时,把要修改的id存入memcache,若其他用户触发修改此id的数据时,读到memcache有这个id的值时,就阻止那个用户修改。

4、实际应用中,并不是让mysql去直面大并发读写,会借助“外力”,比如缓存、利用主从库实现读写分离、分表、使用队列写入等方法来降低并发读写。

猜你喜欢

转载自blog.csdn.net/u013984781/article/details/102454730