目录
1、数据库及引擎
(1)数据类型
(2)增删改查操作
(3)没有框架的情况下:JDBC数据库操作:
(4)关系型和非关系型数据库:
(5)Innodb引擎和MyIASM引擎:
1、数据库及引擎
(1)数据类型
数据库常见的数据类型: | |
int | 整数 |
varchar | 指定最大多长的字符串(如密码等不知道有多长,指定最大长度) |
datetime | 时间的数据结构 |
float(m,d) | 浮点类型 |
text,65535 | 比较大的文本 |
(2)增删改查操作
CRUD操作:增删改查操作 | |
INSERT | INSERT INTO table_name(列1,列2,….) VALUES(值1,值2) |
DELETE | SELECT */列名称 FROM 表名称 |
UPDATE | UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值 |
SELECT | DELETE FROM 表名称 WHERE 列名称 = 值 |
(3)没有框架的情况下:JDBC数据库操作:
- 加载驱动
- 创建数据库连接
- 创建Statement对象(表达式)
- 定义操作的SQL语句
- 执行数据库操作
- 获取并操作结果集
- 关闭对象,回收数据库资源(关闭结果集-->关闭数据库操作对象-->关闭连接)
(4)关系型和非关系型数据库:
非关系型数据库的优势:
1、NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。
2、可扩展性同样也是基于键值对,数据之间没有耦合性,所以非常容易水平扩展。
关系型数据库的优势:
1、复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。
2、事务支持使得对于安全性很高的数据访问要求得以实现。
(5)Innodb引擎和MyIASM引擎:
主要是因为InnoDB支持事务,NDB也支持事务但是主要用于集群中。MySQL能不做集群就不做集群,因为集群要考虑分布式的事务;
区别:
1、MyIASM是非事务安全的,而Innodb是事务安全的。
2、MyIASM是表级的,而InnoDB支持行级锁
3、MyIASM不支持外键,InnoDB支持外键
4、MyIASM支持全文类型(FullText)索引,而InnoDB不支持全文类型索
5、MyIASM保存了表的行数,InnoDB没有保存表的行数。
6、MyIASM相对简单,效率上要优于InnoDB,小型应用可以考虑使用MyIASM
应用场景:
1、InnoDB用于事务处理,具有ACID事务支持等特性,如果在应用中执行大量insert和update操作,应该选择InnoDB
2、MyIASM管理非事务表,提供高速存储和检索以及全文搜索能力,如果在应用中执行大量select操作,应该选择MyIASM
3、对于一般的Web应用来说,应该选择MyIASM,效率更高,特定场景再用InnoDB。
2、事务
事务是由N步数据操作序列组成的逻辑执行单元,这系列操作要么全执行,要么全放弃执行;
事务的特性:(ACID)
原子性:事务是应用中不可再分的最小执行体
一致性:事务执行结果,需使数据从一个一致性状态,变为另一个一致性状态
隔离性:各个事务的执行互不干扰,任何事务的内部操作对其他的事务都是隔离的
持久性:事务一旦提交,对数据所做的任何改变都要记录到永久存储器中。
服务器是在一个多线程的环境中的:事务的隔离性:
--常见的并发异常(5种):
第一类丢失更新;第二类丢失更新;
脏读、不可重复读、幻读;
第一类丢失更新:某一个事务的回滚,导致另外一个事务已更新的数据丢失了;
第二类丢失更新:某一个数据的提交,导致另外一个事务已更新的数据丢失了;
脏读:某一个事务读取了另外一个事务未提交的数据;
不可重复读:对某一个事务,对同一个数据前后读取的结果不一致
幻读:对某一个事务,对同一个表前后查询的行数不一致;
--常见的隔离级别(4种):
Read Uncommitted:读取未提交的数据
Read Committed:读取已提交的数据
Repeatable Read:可重复读(InnoDB默认隔离级别)
Serializable:串行化(加锁,性能极不好)
大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。Mysql的默认隔离级别是Repeatable read。
隔离级别 | 第一类丢失更新 | 脏读 | 第二类丢失更新 | 不可重复读 | 幻读 |
Read Uncommitted | no | no | no | no | no |
Read Committed | ok | ok | no | no | no |
Repeatable Read | ok | ok | ok | ok | no |
Serializable | ok | ok | ok | ok | ok |
3、锁
(1)锁的类型与范围
悲观锁(数据库的锁):1)共享锁(S锁):事务A对某数据加了共享锁以后,其他事务只能对该数据加共享锁,但不能加排他锁。
2)排他锁(X锁):事务A对某数据加了排它锁以后,其他事务对该数据既不能加共享锁,也不能加排它锁。
间隙锁:行级,使用范围条件查询时,对范围内不存在的记录加锁。一是为了防止幻读,二是为了满足恢复和复制的需要。
乐观锁(自定义):使用版本号、时间戳等;在更新数据前,检查版本号是否发生变化。若变化则取消本次更新,否则就更新数据(版本号+1)。
乐观锁的实现:
1. 版本号机制:UPDATE … SET …,VERSION=#{version+1} WHERE … AND VERSION=${version}
2. CAS算法(Compare and swap):是一种无锁的算法,该算法涉及3个操作数(内存值V、旧值A、新值B),当V等于A时,采用原子方式用B的值更新V的值。该算法通常采用自旋操作,也叫自旋锁。它的缺点是:
1) ABA问题:某线程将A该为B,再改回A,则CAS会误认为A没被修改过。
2) 自旋操作采用循环的方式实现,若加锁时间长,则会给CPU带来巨大的开销。
3) CAS只能保证一个共享变量的原子操作。实例:
1.数据库表设计task有三个字段,分别是id、value、version
2.实现:
1)先读task表的数据(实际上这个表只有一条记录),得到version的值为versionValue
2)每次更新task表中的value字段时,为了防止发生冲突,需要这样操作:
update task set value = newValue,version = versionValue +1 where version = versionValue;
只有这条语句执行了,才表明本次更新value字段的值成功。如假设有两个节点A和B都要更新task表中的value字段值,差不多在同一时刻,A节点和B节点从task表中读到的version值为2,那么A节点和B节点在更新value字段值的时候,都操作 update task set value = newValue,version = 3 where version = 2; 实际上只有1个节点执行该SQL语句成功,假设A节点执行成功,那么此时task表的version字段的值是3,B节点再操作update task set value = newValue,version = 3 where version = 2;这条SQL语句是不执行的,这样就保证了更新task表时不发生冲突
(2)加锁操作:
增加行级锁之前,InnoDB会自动给表加意向锁;
执行DML语句时,InnoDB会自动给数据加排它锁;DML语句是指:增删改表中的数据;
执行DQL语句时的加锁:DQL语句是指:查询表中的记录:
共享锁:select…from…where…lock in share mode
排他锁:select…from…where…for update
间隙锁:上述SQL语句采用范围条件时,InnoDB对不存在的记录自动增加间隙锁
(3)死锁
解决方案:
1、一般InnoDB会自动检测,并使一个事务回滚,另一个事务继续;
2、设置超时等待参数:innodeb_lock_wait_timeout
应该在程序中尽量避免死锁的发生:
1、不同业务并发访问多个表时,应约定以相同的顺序来访问这些表
2、以批量的方式处理数据,应事先对数据排序,保证线程按固定的顺序来处理数据
3、在事务中,如果要更新记录,应直接申请足够级别的锁,即排它锁。
二级索引&非唯一索引,如果某个叶子节点(若不存在则假设它存在)被添加了排它锁,那么就会在该叶子节点周围添加间隙锁,如果刚好其它会话要在间隙锁范围内创建索引,那么则会被挂起。主键索引& 唯一索引,如果要添加排它锁的节点存在,不会触发间隙锁,如果不存在,则触发间隙锁。
幻读和不可重复读从结果来看,都是针对一个事务中,多次读取的结果与目标结果不一致。从解决的目的上,是区分不了幻读和不可重复读的。那么从范围上去划分。从范围上区划分,分为指定行多次读取结果不一致,和范围读取结果不一致。指定行多次读取,为了解决结果不一致的问题,最简单的办法是加锁(悲观锁)和多版本控制(MVCC)(乐观锁)来实现。我(个人理解)称指定行多次读取结果不一致,叫做不可重复读。范围多次读取,这样简单的添加行锁是不可能解决多次读取结果不一致的问题,因此需要在范围添加锁。我(个人理解)称范围多次读取结果不一致的问题,叫做幻读。从索引的概念来讲,可以明确锁定某个索引的,且该索引值不会出现重复的,就是不可重复读的问题,不能明确锁定某个索引、或者某个索引值是可重复的,那么就会触发间隙锁,也是幻读的问题。
4、索引
见我的另一篇分析文章:https://blog.youkuaiyun.com/liuzewei2015/article/details/99699015
5、Spring事务管理:
声明式事务:通过XMl配置,声明某方法的事务特征;通过注解,声明某方法的事务特征。
编程式事务:通过TransactionTemplate管理事务,并通过它执行数据库的操作。
// REQUIRED: 支持当前事务(外部事务),如果不存在则创建新事务.
// REQUIRES_NEW: 创建一个新事务,并且暂停当前事务(外部事务).
// NESTED: 如果当前存在事务(外部事务),则嵌套在该事务中执行(独立的提交和回滚),否则就会REQUIRED一样.
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public Object save1() {
// 新增用户
User user = new User();
user.setUsername("alpha");
user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
user.setPassword(CommunityUtil.md5("123" + user.getSalt()));
user.setEmail("alpha@qq.com");
user.setCreateTime(new Date());
userMapper.insertUser(user);
// 新增帖子
DiscussPost post = new DiscussPost();
post.setUserId(user.getId());
post.setTitle("Hello");
post.setContent("新人报道!");
post.setCreateTime(new Date());
discussPostMapper.insertDiscussPost(post);
Integer.valueOf("abc");//故意写一个错误的语句,这条语句报错;事务回滚,对数据库仍然保证一致性
return "ok";
}
public Object save2() {
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
return transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
// 新增用户
User user = new User();
user.setUsername("beta");
user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
user.setPassword(CommunityUtil.md5("123" + user.getSalt()));
user.setEmail("beta@qq.com");
user.setCreateTime(new Date());
userMapper.insertUser(user);
// 新增帖子
DiscussPost post = new DiscussPost();
post.setUserId(user.getId());
post.setTitle("你好");
post.setContent("我是新人!");
post.setCreateTime(new Date());
discussPostMapper.insertDiscussPost(post);
Integer.valueOf("abc");
return "ok";
}
});
}
@Autowired
private AlphaService alphaService;
@Test
public void testSave1() {
Object obj = alphaService.save1();
System.out.println(obj);
}
@Test
public void testSave2() {
Object obj = alphaService.save2();
System.out.println(obj);
}