hibernater的二级缓存
缓存
缓存(Cache): 计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写硬盘(永久性数据存储源)的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存
缓存:程序<–(内存)–>硬盘
什么是二级缓存
- hibernate 提供缓存机制:一级缓存、二级缓存
- 一级缓存:session级别缓存,在一个session中共享数据。
- 二级缓存:sessionFactory级别缓存,整个应用程序共享一个会话工厂,共享一个二级缓存。
- SessionFactory的缓存两部分:
- 内置缓存:使用一个Map,用于存放配置信息,预定义SQL语句等,提供给Hibernate框架自己使用,对外只读的。不能操作。
- 外置缓存:使用另一个Map,用于存放用户自定义数据。默认不开启。外置缓存hibernate只提供规范(接口),需要第三方实现类。外置缓存有成为二级缓存。
二级缓存应用场景
- 适合放入二级缓存中的数据:
- 很少被修改
- 经常被访问
- 不是很重要的数据, 允许出现偶尔的并发问题
- 不适合放入二级缓存中的数据:
- 经常被修改
- 财务数据, 绝对不允许出现并发问题
二级缓存的提供商
- EHCache: 可作为进程(单机)范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 对 Hibernate 的查询缓存提供了支持。–支持集群,redies
- OpenSymphony `:可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 提供了丰富的缓存数据过期策略, 对 Hibernate 的查询缓存提供了支持
- SwarmCache: 可作为集群范围内的缓存, 但不支持 Hibernate 的查询缓存
- JBossCache:可作为集群范围内的缓存, 支持 Hibernate 的查询缓存
配置二级缓存
<!-- 1.开启二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 2.确定二级缓存的供应商 -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<!-- 映射文件 -->
<mapping resource="com/tamakiakoo/entity/User.hbm.xml" />
<mapping resource="com/tamakiakoo/entity/Address.hbm.xml" />
<!-- 3.设置要缓存的对象 -->
<!--
class的顺序是在映射文件之后的
class:配置要缓存的对象.
usage:缓存中的数据是否只读,一般都是read-only
-->
<class-cache usage="read-only" class="com.tamakiakoo.entity.User"/>
<!-- 缓存一个集合,把集合中装额对象也要缓存,不能只缓存集合,而不缓存集合里面的对象 -->
<class-cache usage="read-only" class="com.tamakiakoo.entity.Address"/>
<collection-cache usage="read-only" collection="com.tamakiakoo.entity.User.addresses"/>
<!-- class的顺序是在映射文件之后的-->
<class-cache usage="read-only" class="com.tamakiakoo.entity.User"/>
error:org.xml.sax.SAXParseException; lineNumber: 54; columnNumber: 20; 元素类型为 "session-factory" 的内容必须匹配
"(property*,mapping*,(class-cache|collection-cache)*,event*,listener*)"。
其中在hibernate.cfg.xml文件中配置二级缓存提供商
配置文件详解
defaultCache
maxElementsInMemory="10000"
maxElementsOnDisk="0"
eternal="true"
overflowToDisk="true"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
diskSpoolBufferSizeMB="50"
memoryStoreEvictionPolicy="LFU"/>
- maxElementsInMemory(正整数): 在内存中缓存的最大对象数量
- maxElementsOnDisk(正整数): 在磁盘上缓存的最大对象数量,默认值为0,表示不限制。
- eternal: 设定缓存对象保存的永久属性,默认为 false 。当为 true 时 timeToIdleSeconds、timeToLiveSeconds 失效。
- timeToIdleSeconds(单位:秒): 对象空闲时间,指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问。
- timeToLiveSeconds(单位:秒): 对象存活时间,指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问。
- overflowToDisk: 如果内存中数据超过内存限制,是否要缓存到磁盘上。
- diskPersistent: 是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false。
- diskSpoolBufferSizeMB(单位:MB): DiskStore使用的磁盘大小,默认值30MB。每个cache使用各自的DiskStore。
- memoryStoreEvictionPolicy: 如果内存中数据超过内存限制,向磁盘缓存时的策略。默认值LRU,可选FIFO、LFU。
清空策略
- FIFO(first in first out): 先进先出
- LFU(Less Frequently Used): 最少被使用,缓存的元素有一个hit属性,hit值最小的将会被清除缓存。
- LRU(Least Recently Used)默认策略: 最近最少使用,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清除缓存。
Hibernate二级缓存的并发访问策略
Hibernate二级缓存的并发访问策略有四种:只读(read-only)、非严格读写(nonstrict-read-write)、读写(read-write)和事务(transactional)。但是目前还没有二级缓存提供者完全支持所有的并发访问策略。
方式 | 说明 |
---|---|
只读(read-only): | 对于永远不会被修改的数据可以采用这种并发访问策略,它的并发性能是最高的。但必须保证数据不会被修改,否则就会出错。 |
非严格读写(nonstrict-read-write): | 非严格读写不能保证缓存与数据库中数据的一致性,如果存在两个事务并发地访问缓存数据的可能,则应该为该数据配置一个很短的过期时间,以减少读脏数据的可能。对于极少被修改,并且可以容忍偶尔脏读的数据可以采用这种并发策略。 |
读写(read-write): | 读写策略提供了“read committed"数据库隔离级别。对于经常被读但很少修改的数据可以采用这种策略,它可以防止读脏数据。 |
事务(transactional): | 它提供了Repeatable Read事务隔离级别。它可以防止脏读和不可重复读这类的并发问题 |
例如:
<class-cache usage="read-only" class="com.tamakiakoo.entity.User"/>
测试
package
user.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.tamakiakoo.entity">
<class name="User" table="t_user" lazy="false">
<id name="id" column="id">
<generator class="native" />
</id>
<property name="username" />
<property name="password" />
<property name="email" />
<property name="sex" />
<set name="addSet">
<key column="user_id" ></key>
<one-to-many class="Address" />
</set>
</class>
</hibernate-mapping>
错误
这种二级缓冲的错误一般是由于 二级缓存没有开启,或者供应商没有写
hibernate.cfg.xml
注意: 已经注释掉了class-cache
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 链接数据库的四个信息 -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/user</property>
<property name="connection.username">root</property>
<property name="connection.password">123456</property>
<!-- 连接池-->
<property name="connection.pool_size">1</property>
<!-- 方言(每个数据库多有自己的方言)-->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 把session和当前线程绑定 -->
<property name="current_session_context_class">thread</property>
<!-- 是否显示sql-->
<property name="show_sql">true</property>
<!-- 1.开启二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 2.确定二级缓存的供应商 -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<property name="hbm2ddl.auto">update</property>
<mapping resource="com/tamakiakoo/entity/User.hbm.xml" />
<mapping resource="com/tamakiakoo/entity/Address.hbm.xml" />
<!--
<class-cache usage="read-only" class="com.tamakiakoo.entity.User"/>
<class-cache usage="read-only" class="com.tamakiakoo.entity.Address"/>
-->
</session-factory>
</hibernate-configuration>
测试代码
@Test
public void testCache(){
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tr = session.beginTransaction();
User user = (User)session.get(User.class, 179);
System.out.println(user.getUsername());
tr.commit();
System.out.println("-------------------------");
Session session1 = HibernateUtil.getSessionFactory().openSession();
Transaction tr1 = session1.beginTransaction();
User user1 = (User)session1.get(User.class, 179);
System.out.println(user1.getUsername());
tr1.commit();
}
运行
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_,
user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
admin
-------------------------
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_,
user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
admin
结论:
没有开启class-cache
并不会缓存,因为Hibernate不知道你要缓存的对象是谁
开启class-cache
<!--
<class-cache usage="read-only" class="com.tamakiakoo.entity.User"/>
<class-cache usage="read-only" class="com.tamakiakoo.entity.Address"/>
-->
再次运行
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
admin
-------------------------
admin
继续测试
第一次get的时候不获取集合,第二次获取集合
@Test
public void testCache3(){
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tr = session.beginTransaction();
User user = (User)session.get(User.class, 179);
System.out.println(user.getUsername());
tr.commit();
System.out.println("-------------------------");
Session session1 = HibernateUtil.getSessionFactory().openSession();
Transaction tr1 = session1.beginTransaction();
User user1 = (User)session1.get(User.class, 179);
System.out.println(user1.getUsername()+user1.getAddSet().size());
tr1.commit();
System.out.println("-------------------------");
Session session2 = HibernateUtil.getSessionFactory().openSession();
Transaction tr2 = session2.beginTransaction();
User user2 = (User)session2.get(User.class, 179);
System.out.println(user2.getUsername()+user2.getAddSet().size());
tr1.commit();
}
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
admin
-------------------------
Hibernate: select addset0_.user_id as user4_0_1_, addset0_.id as id1_, addset0_.id as id1_0_, addset0_.address as address1_0_, addset0_.phone as phone1_0_, addset0_.user_id as user4_1_0_ from t_address addset0_ where addset0_.user_id=?
admin3
-------------------------
Hibernate: select addset0_.user_id as user4_0_1_, addset0_.id as id1_, addset0_.id as id1_0_, addset0_.address as address1_0_, addset0_.phone as phone1_0_, addset0_.user_id as user4_1_0_ from t_address addset0_ where addset0_.user_id=?
admin3
说明:
set:很积极,用到的时候就去查询
修改配置文件
增加collection-cache
配置文件
<class-cache usage="read-only" class="com.tamakiakoo.entity.Address"/>
<collection-cache配置文件 usage="read-only" collection="com.tamakiakoo.entity.User.addSet"/>
再次运行
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
admin
-------------------------
Hibernate: select addset0_.user_id as user4_0_1_, addset0_.id as id1_, addset0_.id as id1_0_, addset0_.address as address1_0_, addset0_.phone as phone1_0_, addset0_.user_id as user4_1_0_ from t_address addset0_ where addset0_.user_id=?
admin3
-------------------------
admin3
说明
只有配置了collection-cache
才会缓存集合,其中collection="com.tamakiakoo.entity.User.addSet"
addSet是实体类集合的名字
再次测试
利用hql查询
@Test
public void testCache2(){
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tr = session.beginTransaction();
Query query = session.createQuery("from User");
List<User> list = query.list();
System.out.println(list.size());
tr.commit();
System.out.println("-------------------------");
Session session1 = HibernateUtil.getSessionFactory().openSession();
Transaction tr1 = session1.beginTransaction();
Query query1 = session1.createQuery("from User");
List<User> list1 = query1.list();
System.out.println(list1.size());
tr1.commit();
}
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_
6
-------------------------
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_
6
说明 :利用二级缓存,hql查询在不同同session对象中进行查询相同对象并不会在二级缓存中进行缓存.
继续测试
同一个session 使用hql查询
@Test
public void testCache2(){
Session session1 = HibernateUtil.getSessionFactory().openSession();
Transaction tr1 = session1.beginTransaction();
Query query1 = session1.createQuery("from User");
List<User> list1 = query1.list();
System.out.println(list1.size());
Query query2 = session1.createQuery("from User");
List<User> list2 = query2.list();
System.out.println(list2.size());
tr1.commit();
}
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_
6
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_
6
说明 :利用二级缓存,hql查询在同一个session对象中进行查询相同对象并不会在二级缓存中进行缓存.
三级缓存(查询缓存)
查询缓存又称为三级缓存
查询缓存默认不使用。需要手动开启
使用步骤
- 开启二级缓存
- 在查询query对象,设置缓存内容(注意:存放和查询 都需要设置)
- 二级缓存存放元数据和预定义的SQL。
<property name="hibernate.cache.use_query_cache">true</property>
开启查询缓
- 需要先开启二级缓存
- 确定二级缓存的供应商
hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 链接数据库的四个信息 -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/1901_mysql</property>
<property name="connection.username">root</property>
<property name="connection.password">root</property>
<!-- 连接池 -->
<property name="connection.pool_size">1</property>
<!-- 方言(每个数据库多有自己的方言) -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 把session和当前线程绑定 -->
<property name="current_session_context_class">thread</property>
<!-- 是否显示sql -->
<property name="show_sql">true</property>
<!-- 是否是格式化 -->
<property name="format_sql">true</property>
<property name="hbm2ddl.auto">update</property>
<!-- 1.开启二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 2.确定二级缓存的供应商 -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<!-- 开启查询缓存,开启二级缓存,确定二级缓存的供应商 -->
<property name="hibernate.cache.use_query_cache">true</property>
<!-- 映射文件 -->
<mapping resource="com/qf/entity/User.hbm.xml" />
<mapping resource="com/qf/entity/Address.hbm.xml" />
<!-- <class-cache usage="read-only" class="com.qf.entity.User"/> -->
</session-factory>
</hibernate-configuration>
使用查询缓冲需要开启开关
开启开关query1.setCacheable(true);
执行代码
@Test
public void testCache2(){
Session session1 = HibernateUtil.getSessionFactory().openSession();
Transaction tr1 = session1.beginTransaction();
Query query1 = session1.createQuery("from User");
query1.setCacheable(true);
List<User> list1 = query1.list();
System.out.println(list1.size());
Query query2 = session1.createQuery("from User");
query2.setCacheable(true);
List<User> list2 = query2.list();
System.out.println(list2.size());
tr1.commit();
}
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_
6
6
只有配置了 查询缓冲和二级缓存和开关(每个query都也开启,还有缓存的对象也好配置)才会进行缓存
继续测试注释掉–>缓存对象
<!-- <class-cache usage="read-only" class="com.tamakiakoo.entity.User"/>
<class-cache usage="read-only" class="com.tamakiakoo.entity.Address"/>
<collection-cache usage="read-only" collection="com.tamakiakoo.entity.User.addSet"/> -->
执行代码
注意: 这里用的session是同一个的
/**
* 这个是缓存在session级别
*/
@Test
public void testCache7() {
Session s1 = HibernateUtil.getSessionFactory().openSession();
Query query = s1.createQuery("from User u where u.id = :id");
query.setCacheable(true); // 打开开关
query.setInteger("id", 177);
System.out.println(query.uniqueResult());
System.out.println("===========================================");
Query query2 = s1.createQuery("from User u where u.id = :id");
query2.setCacheable(true); // 打开开关
query2.setInteger("id", 177);
System.out.println(query2.uniqueResult());
}
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_ where user0_.id=?
User [id=177, username=admin, password=admin, email=null, sex=null]
===========================================
User [id=177, username=admin, password=admin, email=null, sex=null]
@Test
public void testCache2(){
Session session1 = HibernateUtil.getSessionFactory().openSession();
Transaction tr1 = session1.beginTransaction();
Query query1 = session1.createQuery("from User");
query1.setCacheable(true);
List<User> list1 = query1.list();
System.out.println(list1.size());
Query query2 = session1.createQuery("from User");
query2.setCacheable(true);
List<User> list2 = query2.list();
System.out.println(list2.size());
tr1.commit();
}
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_
6
6
有缓存
继续测试
执行代码
注意: 这里用的session是不同的的
/**
* 缓存在sessionFactory级别的
*/
@Test
public void testCache6() {
Session s1 = HibernateUtil.getSessionFactory().openSession();
Query query = s1.createQuery("from User u where u.id = :id");
query.setCacheable(true); // 打开开关
query.setInteger("id", 177);
System.out.println(query.uniqueResult());
System.out.println("=========================================");
Session s2 = HibernateUtil.getSessionFactory().openSession();
Query query2 = s2.createQuery("from User u where u.id = :id");
query2.setCacheable(true);
query2.setInteger("id", 177);
System.out.println(query2.uniqueResult());
}
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_ where user0_.id=?
User [id=177, username=admin, password=admin, email=null, sex=null]
=========================================
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
User [id=177, username=admin, password=admin, email=null, sex=null]
@Test
public void testCache2(){
Session session1 = HibernateUtil.getSessionFactory().openSession();
Query query1 = session1.createQuery("from User");
query1.setCacheable(true);
List<User> list1 = query1.list();
System.out.println(list1.size());
Session session3 = HibernateUtil.getSessionFactory().openSession();
Query query3 = session3.createQuery("from User");
query3.setCacheable(true);
List<User> list3 = query3.list();
System.out.println(list3.size());
}
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_
6
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
6
总结
- 查询缓存:只要是针对HQL的查询
- session:
- 开启查询缓存
- 二级缓存
- 确定二级供应商
- 打开开关
- sessionFactory:
- 开启查询缓存
- 二级缓存
- 确定二级供应商
- 确定缓存对象
- 打开开关
- session:
去重复 distinct和group by
语句
SELECT
user0_.id AS id0_0_,
addset1_.id AS id1_1_,
user0_.username AS username0_0_,
user0_. PASSWORD AS password0_0_,
user0_.email AS email0_0_,
user0_.sex AS sex0_0_,
addset1_.address AS address1_1_,
addset1_.phone AS phone1_1_,
addset1_.user_id AS user4_1_1_,
addset1_.user_id AS user4_0_0__,
addset1_.id AS id0__
FROM
t_user user0_
LEFT OUTER JOIN t_address addset1_ ON user0_.id = addset1_.user_id
查询结果
Mysql数据库中查询重复数据和去重数据 , 删除重复数据的sql及分析
CREATE TABLE `user` (
`id` bigint(255) NOT NULL AUTO_INCREMENT,
`name` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '名称',
`age` int(2) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
查看重复的数据
SELECT id,`name`,age,count(1)
FROM user GROUP BY `name`,age
SELECT id,`name`,age,count(1) as c
FROM user GROUP BY `name`,age having c > 1
删除重复的数据留下一条
SELECT MIN(id) FROM user
GROUP BY name,age
查询出来的id就是我们需要留下的不重复的数据的id
按理来说只要:
delete from user where id not in 子语句1
DELETE FROM user
WHERE id NOT IN (
SELECT MIN(id) FROM user
GROUP BY name,age
)
但是报错了
DELETE FROM user
WHERE id NOT IN (
SELECT MIN(id) FROM user
GROUP BY name,age
)
> 1093 - You can't specify target table 'user' for update in FROM clause
因为在mysql中,不能在一条Sql语句中,即查询这些数据,同时修改这些数据
解决办法
DELETE FROM user
WHERE id NOT IN (
SELECT temp.min_id FROM (
SELECT MIN(id) min_id FROM user
GROUP BY name,age
)AS temp
);
select * from user;
插入MySQL数据库前去除重复数据的几种方法
可以通过下面几个方法进行处理:
-
- 使用Ingore关键字
- INSERT IGNORE INTO
- ignore关键字所修饰的SQL语句执行后,在遇到主键冲突时会返回一个0,代表并没有插入此条数据。如果主键是由后台生成的(如uuid),我们可以通过判断这个返回值是否为0来判断主键是否有冲突,从而重新生成新的主键key。
- 使用Ingore关键字
- 使用on duplicate key update关键字,如插入数据时发生主键冲突就更新数据
- insert into device values (1,2) ON DUPLICATE KEY UPDATE status =‘rowname’;
- 插入一条数据(1,2),当有重复的主键KEY,那就更新rowname。
- 使用replace into关键字
- REPLACE INTO
hibernate的事务管理
什么是事务?事务就是一组操作,只有整个操作在一起才算是一个事务
事务的特性ACID
- A(atomicity):原子性:事务不可被划分,是一个整体,要么一起成功,要么一起失败
- C(consistence):一致性,A转100给B,A减少了100,那么B就要增加100,增加减少100就是一致的意思
- I(isolation):隔离性,多个事务对同一内容的并发操作。
- D(durability):持久性,已经提交的事务,就已经保存到数据库中,不能在改变了。
多个事务对同一内容同时进行操作,那么就会出现一系列的并发问题。
- 脏读:一个事务 读到 另一个事务 没有提交的数据。
- 不可重复读:一个事务 读到 另一个事务 已经提交的数据(update更新语句)
- 虚度(幻读):一个事务 读到 另一个事务 已经提交的数据(insert插入语句)
事务隔离级别,用于解决隔离问题
- read uncommitted(1) :读未提交,一个事务 读到 另一个事务 没有提交的数据,存在问题3个,解决0个问题
- read committed(2):读已提交,一个事务 读到 另一个事务 已经提交的数据,存在问题2个,解决1个问题(脏读问题)
- repeatable read(4):可重复读,一个事务 读到重复的数据,即使另一个事务已经提交。存在问题1个,解决2个问题(脏读、不可重复读)
- serializable(8):单事务,同时只有一个事务可以操作,另一个事务挂起(暂停),存在问题0个,解决3个问题(脏读、不可重复读、虚读)
查看MySQL默认隔离级别:select @@global.tx_isolation,@@tx_isolation;
mysql> set global transaction isolation level read committed; //全局的
mysql> set session transaction isolation level read committed; //当前会话
hibernate中设置事务隔离级别
<!-- 设置数据库的隔离级别 -->
<property name="hibernate.connection.isolation">4</property>
锁
为什么需要锁(并发控制)?
在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突。这就是著名的并发性问题。
乐观锁
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改.所以不会上锁,上锁之后,当别的线程想要拿数据时,就会阻塞,直到给数据上锁的线程将事务提交或者回滚。传统的关系型数据库里就用到了很多这种锁机制,比如行锁,表锁,共享锁,排他锁等,都是在做操作之前先上锁。
创建一张表时添加一个version字段,表示是版本号
两个人同时修改名字
其中一方修改完之后就让版本号自增+1
然后另一方发现版本号不一致就不提交
行锁
又分共享锁和排他锁,由字面意思理解,就是给某一行加上锁,也就是一条记录加上锁。
注意: 行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁。
排他锁:
名词解释:若某个事物对某一行加上了排他锁,只能这个事务对其进行读写,在此事务结束之前,其他事务不能对其进行加任何锁,其他进程可以读取,不能进行写操作,需等待其释放。
select * from student for update;
左边的线程,在事务中通过select for update语句给sid = 1的数据行上了锁。右边的线程此时可以使用select语句读取数据,但是如果也使用select for update语句,就会阻塞,使用update,add,delete也会阻塞。
当左边的线程将事务提交(或者回滚),右边的线程就会获取锁,线程不再阻塞
这样别人想拿这个数据就会lock直到它拿到锁。
共享锁
共享锁又称为读锁,一个线程给数据加上共享锁后,其他线程只能读数据,不能修改。
SELECT * from city where id = "1" lock in share mode;
然后在另一个查询窗口中,对id为1的数据进行更新
update city set name="666" where id ="1";
报错
[SQL]update city set name="666" where id ="1";
[Err] 1205 - Lock wait timeout exceeded; try restarting transaction
那么证明,对于id=1的记录加锁成功了,在上一条记录还没有commit之前,这条id=1的记录被锁住了,只有在上一个事务释放掉锁后才能进行操作,或用共享锁才能对此数据进行操作。
再实验一下:
update city set name="666" where id ="1" lock in share mode;
[Err] 1064 - You have an error in your SQL syntax; check the manual
corresponds to your MySQL server version for the right syntax to use near 'lock in
share mode' at line 1
加上共享锁后,也提示错误信息了,对于update,insert,delete语句会自动加排它锁
试了试
SELECT * from city where id = "1" lock in share mode;
结果ok
总结:
- 解决并反问题的(多个线程访问同一块资源的问题)
- 行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁
- 读取频繁使用乐观锁,写入频繁使用悲观锁。
- 悲观锁:
- 修改数据的时候总会认为别人也会修改
- 原理:
for update
(数据库中锁机制)
- 乐观锁:在修改数据的时候总会认为别人不会修改
- 原理:通过版本号控制(增加一个vision的字段)
- 在提交的时候判断版本哈是否一致
- 如果一致就提交,并且版本号自增
- 如果不一致就不让提交,说明数据已经被其他的事务修改过了
- 共享锁又称为读锁,一个线程给数据加上共享锁后,其他线程只能读数据,不能修改。
- 原理
lock in share mode
- 原理
HIbernate锁 锁
乐观锁
Hibernate如果要使用乐观锁需要配置
模拟乐观锁
报错
悲观观锁
Hibernate二级缓存的并发访问策略
Hibernate二级缓存的并发访问策略有四种:只读(read-only)、非严格读写(nonstrict-read-write)、读写(read-write)和事务(transactional)。但是目前还没有二级缓存提供者完全支持所有的并发访问策略。
方式 | 说明 |
---|---|
只读(read-only): | 对于永远不会被修改的数据可以采用这种并发访问策略,它的并发性能是最高的。但必须保证数据不会被修改,否则就会出错。 |
非严格读写(nonstrict-read-write): | 非严格读写不能保证缓存与数据库中数据的一致性,如果存在两个事务并发地访问缓存数据的可能,则应该为该数据配置一个很短的过期时间,以减少读脏数据的可能。对于极少被修改,并且可以容忍偶尔脏读的数据可以采用这种并发策略。 |
读写(read-write): | 读写策略提供了“read committed"数据库隔离级别。对于经常被读但很少修改的数据可以采用这种策略,它可以防止读脏数据。 |
事务(transactional): | 它提供了Repeatable Read事务隔离级别。它可以防止脏读和不可重复读这类的并发问题 |