基础知识

基本概念

数据模型描述的内容包括三个部分:数据结构、数据操作和数据约束

乐观锁和悲观锁

  • 乐观锁:乐观锁在操作数据时认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据,如果别人修改了数据则放弃操作,否则执行操作。

  • 悲观锁:悲观锁在操作数据时认为别人会同时修改数据。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。

CAS 的参数一共有三个,第一个是内存地址,第二是期望值,第三个是新值。如果内存处的值与期望值相等就进行更新,否则退出。CAS 的操作是原子性的。一般将 CAS 配合 yield + while 对内存进行更新

原子变量就是乐观锁

乐观锁适用于单个变量,悲观锁适用于多个变量

乐观锁在竞争不激烈时具有优势,悲观锁在竞争激烈时更有优势。因为乐观锁中存在忙等

乐观锁本身不加锁,但是可以和悲观锁相配合

CAS 的缺点:

  • ABA问题

    假设有两个线程——线程1和线程2,两个线程按照顺序进行以下操作:

    1. 线程1读取内存中数据为A;

    2. 线程2将该数据修改为B;

    3. 线程2将该数据修改为A;

    4. 线程1对数据进行CAS操作

    在第(4)步中,由于内存中数据仍然为A,因此CAS操作成功,但实际上该数据已经被线程2修改过了。这就是ABA问题。

    在AtomicInteger的例子中,ABA似乎没有什么危害。但是在某些场景下,ABA却会带来隐患,例如栈顶问题:一个栈的栈顶经过两次(或多次)变化又恢复了原值,但是栈可能已发生了变化。

    对于ABA问题,比较有效的方案是引入版本号,内存中的值每发生一次变化,版本号都+1;在进行CAS操作时,不仅比较内存中的值,也会比较版本号,只有当二者都没有变化时,CAS才能执行成功。Java中的AtomicStampedReference类便是使用版本号来解决ABA问题的。

  • 高竞争下的开销问题

    在并发冲突概率大的高竞争环境下,如果CAS一直失败,会一直重试,CPU开销较大。针对这个问题的一个思路是引入退出机制,如重试次数超过一定阈值后失败退出。当然,更重要的是避免在高竞争环境下使用乐观锁。

  • 功能限制

    CAS的功能是比较受限的,例如CAS只能保证单个变量(或者说单个内存值)操作的原子性,这意味着:(1)原子性不一定能保证线程安全,例如在Java中需要与volatile配合来保证线程安全;(2)当涉及到多个变量(内存值)时,CAS也无能为力。

    除此之外,CAS的实现需要硬件层面处理器的支持,在Java中普通用户无法直接使用,只能借助atomic包下的原子类使用,灵活性受到限制。

  • 面试官:你了解乐观锁和悲观锁吗?

MySQL 中的锁

存储引擎

行锁

表锁

页锁

MyISAM

\(\checkmark\)

BDB

\(\checkmark\)

\(\checkmark\)

InnoDB

\(\checkmark\)

\(\checkmark\)

  • 行锁是存储引擎实现的作用在索引上的。如果在建表的时候没有定义一个索引,InnoDB 会创建一个聚簇索引并将其作为锁作用的索引

  • 表锁是由 MySQL Server 实现的。因此所有的引擎都支持

两种锁的比较

表锁:加锁过程的开销小,加锁的速度快;不会出现死锁的情况;锁定的粒度大,发生锁冲突的几率大,并发度低;

  • 一般在执行DDL语句时会对整个表进行加锁,比如说 ALTER TABLE 等操作;

  • 如果对InnoDB的表使用行锁,被锁定字段不是主键,也没有针对它建立索引的话,那么将会锁整张表;

  • 表级锁更适合于以查询为主,并发用户少,只有少量按索引条件更新数据的应用,如Web 应用。

行锁:加锁过程的开销大,加锁的速度慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;

  • 最大程度的支持并发,同时也带来了最大的锁开销。

  • 在 InnoDB 中,除单个 SQL 组成的事务外,锁是逐步获得的,这就决定了在 InnoDB 中发生死锁是可能的。

  • 行级锁只在存储引擎层实现,而 MySQL 服务器层没有实现。 行级锁更适合于有大量按索引条件并发更新少量不同数据,又有并发查询的应用,如一些在线事务处理(OLTP)系统。

事务

事务的特征 ACID

分别代指原子性、一致性、隔离性、持久性

数据安全

数据的安全和一致性主要是由于并发引起的,这儿包括脏读、幻读、不可重复读

  • 脏读:事务 A 在更新数据的时候事务 B 读取了数据。但是这时数据还没有更新为正确的值(比如事务失败回滚),事务 B 就发生了脏读。也就是说事务 B 读到了事务 A 未提交的数据

  • 幻读:事务 A 和事务 B 同时对数据进行更新,导致事务 A 更新的数据被事务 B 覆盖

  • 不可重复读:事务 A 两次读取到的数据不一样

级别高低:脏读 < 不可重复读 < 幻读

所以设置了最高级别的SERIALIZABLE_READ就不需要设置其他的了,即解决了幻读问题那么脏度和不可重复读自然就都解决了。

数据库的隔离级别为:

隔离级别

作用

Read Uncommitted

什么也不做

Read Committed

事务提交后才能读取数据,解决幻读

Repeated Read

在一个事务中,对同一份数据的读取结果总是相同的,解决脏读、解决重复读

Serialization

事务串行执行。通过牺牲系统的并发性解决并发事务的所有问题

排它锁和共享锁

  • 排它锁 又称为写锁、X 锁。在事务对数据加锁期间可以进行读写,但是不允许其他事务加锁

  • 共享锁 又称为读锁、S 锁。在事务加锁期间允许读,不允许写。加锁期间允许其它事务加共享锁,但是不允许加写排它锁

数据库安全保护

DBMS对数据库的安全保护方功能是通过四方面实现的,即安全性控制、完整性控制、并发性控制和数据库恢复:

  • 安全性控制:使用用户标识和鉴定、用户存取权限控制、定义视图、数据加密和审计等防止用户绕过 DBMS 直接通过操作系统访问

  • 完整性控制:

  • 并发性控制:

  • 数据库恢复:

连接

分为内连接(等值连接)和外连接

内连接是 INNER JOIN 。左右表取交集 外连接包含了左连接和右连接。 LEFT JOIN 和 RIGHT JOIN

数据库三级模式结构

数据库系统的三级模式结构是指模式、外模式和内模式

  • 模式:逻辑模式或者概念模式。是所有用户的公共数据视图。位于三级结构的中间

  • 外模式:也叫用户模式。是数据库用户能看到的数据视图。外模式是模式的子集,一个模式可以有多个外模式

  • 内模式:一个数据库只有一个内模式。内模式是数据库内部数据的表示方式。

三级模式保证了数据的独立性

索引

索引的好处: 1

  • 数据库索引能避免进行数据库全表的扫描。

  • 特定的情况下,索引可以避免排序操作。

  • 存在非聚集索引时,有时无需访问数据页即可得到数据。

  • 存在聚集索引时,有时无需访问数据页即可得到数据。

备注

建立索引不一定提高查询速度,这取决于索引是否有助于查询。例如对于需要全表扫描的查询而言,有没有索引没什么影响

1

关于索引的说法,以下正确的是

数据库范式

一共是六个范式,1NF→2NF→3NF→BCNF→4NF→5NF

其中:

1NF:数据表中的所有码都是不可再分的 2NF:存在唯一一个主键,且其它关键字与主键一一对应 3NF:非主属性不依赖于非主键属性 BCNF: 4NF:当一个表中的非主属性相互独立时,这些非主属性不应该有多值 5NF:表必须可以分解为较小的表,除非那些表在逻辑上拥有与原始表相同的主键。

主键

根据 2NF 。主键不可为空,主键必须能够代表当前元组的信息

主键可为联合主键

但是对于存在唯一索引的非主键而言,允许一个为空