Serialization Best Practices

Unity Serialization

So you are writing a really cool editor extension in Unity and things seem to be going really well. You get your data structures all sorted out are really happy with how the tool you have written works.

Then you enter and exit play mode.

Suddenly all the data you had entered is gone and your tool is reset to the default, just initialized state. It’s very frustrating! “Why does this happen?” you ask yourself. The reason has to do with how the managed (mono) layer of Unity works. Once you understand it, then things get much easier  :)

What happens when an assembly is reloaded?
When you enter / exit play mode or change a script Unity has to reload the mono assemblies, that is the dll's associated with Unity. 

On the user side this is a 3 step process:
  • Pull all the serializable data out of managed land, creating an internal representation of the data on the C++ side of Unity.
  • Destroy all memory / information associated with the managed side of Unity, and reload the assemblies.
  • Reserialize the data that was saved in C++ back into managed land.

What this means is that for your data structures / information to survive an assembly reload you need to ensure that can get serialized into and out of c++ memory properly. Doing this also means that (with some minor modifications) you can save this data structure to an asset file and reload it at a later date.

How do I work with Unity's serialization?
The easiest way to learn about Unity serialization is by working through an example. We are going to start with a simple editor window, it contains a reference to a class which we want to make survive an assembly reload.

Code (csharp):
  1.  
  2. using UnityEngine;
  3. using UnityEditor;
  4.  
  5. public  class MyWindow  :  EditorWindow
  6. {
  7.      private SerializeMe m_SerialziedThing;
  8.  
  9.      [MenuItem  ( "Window/Serialization" ) ]
  10.      static  void Init  ( )  {
  11.          GetWindow  <MyWindow > ( );
  12.      }
  13.  
  14.      void  OnEnable  ( )
  15.      {
  16.          hideFlags  = HideFlags. HideAndDontSave;
  17.          if  (m_SerialziedThing  ==  null )
  18.             m_SerialziedThing  =  new SerializeMe  ( );
  19.      }
  20.  
  21.      void  OnGUI  ( )  {
  22.          GUILayout. Label  ( "Serialized Things"EditorStyles. boldLabel );
  23.         m_SerialziedThing. OnGUI  ( );
  24.      }
  25. }
  26.  
Code (csharp):
  1.  
  2. using UnityEditor;
  3.  
  4. public  struct NestedStruct
  5. {
  6.      private  float m_StructFloat;
  7.      public  void  OnGUI  ( )
  8.      {
  9.         m_StructFloat  =  EditorGUILayout. FloatField ( "Struct Float", m_StructFloat );
  10.      }
  11. }
  12.  
  13. public  class SerializeMe
  14. {
  15.      private  string m_Name;
  16.      private  int m_Value;
  17.  
  18.      private NestedStruct m_Struct;
  19.  
  20.      public SerializeMe  ( )
  21.      {
  22.         m_Struct  =  new NestedStruct ( );
  23.         m_Name  =  "";
  24.      }
  25.  
  26.      public  void  OnGUI  ( )
  27.      {
  28.         m_Name  =  EditorGUILayout. TextField (  "Name", m_Name );
  29.         m_Value  =  EditorGUILayout. IntSlider  ( "Value", m_Value,  010 );
  30.  
  31.         m_Struct. OnGUI  ( );
  32.      }
  33. }
  34.  
When you run this and force an assembly reload you will notice that any value in the window you have changed will not survive. This is because when the assembly is reloaded the reference to the ‘m_SerialziedThing’ is gone. It is not marked up to be serialized.

There are a few things that need to be done to make this serialization work properly:
In MyWindow.cs:
  • The field ‘m_SerializedThing’ needs to have the attribute [SerializeField] added to it. What this tells Unity is that it should attempt to serialize this field on assembly reload or similar events.

In SerializeMe.cs:
  • The class ‘SerializeMe’ needs to have the [Serializable] attribute added to it. This tells Unity that the class is serializable.
  • The struct ‘NestedStruct’ needs to have the [Serializable] attribute added to it.
  • Each (non public) field that you want to be serialized needs to have the [SerializeField] attribute added to it.

After adding these flags open the window and modify the fields. You will notice that after an assembly reload that the fields retain their values; that is apart from the field that came from the struct. This brings up the first important point, structs are not very well supported for serialization. Changing ‘NestedStruct’ from a struct to a class fixes this issue.

The code now looks like this:
Code (csharp):
  1.  
  2. using UnityEngine;
  3. using UnityEditor;
  4.  
  5. public  class MyWindow  :  EditorWindow
  6. {
  7.      private SerializeMe m_SerialziedThing;
  8.  
  9.      [MenuItem  ( "Window/Serialization" ) ]
  10.      static  void Init  ( )  {
  11.          GetWindow  <MyWindow > ( );
  12.      }
  13.  
  14.      void  OnEnable  ( )
  15.      {
  16.          hideFlags  = HideFlags. HideAndDontSave;
  17.          if  (m_SerialziedThing  ==  null )
  18.             m_SerialziedThing  =  new SerializeMe  ( );
  19.      }
  20.  
  21.      void  OnGUI  ( )  {
  22.          GUILayout. Label  ( "Serialized Things"EditorStyles. boldLabel );
  23.         m_SerialziedThing. OnGUI  ( );
  24.      }
  25. }
  26.  
  27. using System;
  28. using UnityEditor;
  29. using UnityEngine;
  30.  
  31. [Serializable ]
  32. public  class NestedStruct
  33. {
  34.      [SerializeField ]
  35.      private  float m_StructFloat;
  36.      public  void  OnGUI  ( )
  37.      {
  38.         m_StructFloat  =  EditorGUILayout. FloatField ( "Struct Float", m_StructFloat );
  39.      }
  40. }
  41.  
  42. [Serializable ]
  43. public  class SerializeMe
  44. {
  45.      [SerializeField ]
  46.      private  string m_Name;
  47.      [SerializeField ]
  48.      private  int m_Value;
  49.      [SerializeField ]
  50.      private NestedStruct m_Struct;
  51.  
  52.      public SerializeMe  ( )
  53.      {
  54.         m_Struct  =  new NestedStruct ( );
  55.         m_Name  =  "";
  56.      }
  57.  
  58.      public  void  OnGUI  ( )
  59.      {
  60.         m_Name  =  EditorGUILayout. TextField (  "Name", m_Name );
  61.         m_Value  =  EditorGUILayout. IntSlider  ( "Value", m_Value,  010 );
  62.  
  63.         m_Struct. OnGUI  ( );
  64.      }
  65. }
  66.  
Some Serialization Rules
  • Avoid structs
  • Classes you want to be serializable need to be marked with [Serializable]
  • Public fields are serialized (so long as they reference a [Serializable] class)
  • Private fields are serialized under some circumstances (editor).
  • Mark private fields as [SerializeField] if you wish them to be serialized.
  • [NonSerialized] exists for fields that you do not want to serialize

Scriptable Objects
So far we have looked at using normal classes when it comes to serialization. Unfortunately using plain classes has some issues when it comes to serialization in Unity. Lets take a look at an example.

Code (csharp):
  1.  
  2. using System;
  3. using UnityEditor;
  4. using UnityEngine;
  5.  
  6. [Serializable ]
  7. public  class NestedClass
  8. {
  9.      [SerializeField ]
  10.      private  float m_StructFloat;
  11.      public  void  OnGUI ( )
  12.      {
  13.         m_StructFloat  =  EditorGUILayout. FloatField ( "Float", m_StructFloat );
  14.      }
  15. }
  16.  
  17. [Serializable ]
  18. public  class SerializeMe
  19. {
  20.      [SerializeField ]
  21.      private NestedClass m_Class1;
  22.  
  23.      [SerializeField ]
  24.      private NestedClass m_Class2;
  25.  
  26.      public  void  OnGUI  ( )
  27.      {
  28.          if  (m_Class1  ==  null )
  29.             m_Class1  =  new NestedClass  ( );
  30.          if  (m_Class2  ==  null )
  31.             m_Class2  = m_Class1;
  32.  
  33.         m_Class1. OnGUI ( );
  34.         m_Class2. OnGUI ( );
  35.      }
  36. }
  37.  
This is a contrived example to show a very specific corner case of the Unity serialization system that can catch you if you are not careful. You will notice that we have two fields of type NestedClass. The first time the window is drawn it will show both the fields, and as m_Class1 and m_Class2 point to the same reference, modifying one will modify the other.

Now try reloading the assembly by entering and exiting play mode... The references have been decoupled. This is due to how serialization of works when you mark a class as simply [Serializable]

When you are serializing standard classes Unity walks through the fields of the class and serializes each one individually, even if the reference is shared between multiple fields. This means that you could have the same object serialized multiple times, and on deserialization the system will not know they are really the same object. If you are designing a complex system this is a frustrating limitation because it means that complex interactions between classes can not be captured properly.

Enter ScriptableObjects! ScriptableObjects are a type of class that correctly serializes as references, so that they only get serialized once. This allows complex class interactions to be stored in a way that you would expect. Internally in Unity ScriptableObjects and MonoBehaviours are the same; in userland code you can have a ScriptableObject that is not attached to a GameObject; this is different to how MonoBehaviour works. They are great for general data structure serialization.

Let’s modify the example to be able to handle serialization properly:

Code (csharp):
  1.  
  2. using System;
  3. using UnityEditor;
  4. using UnityEngine;
  5.  
  6. [Serializable ]
  7. public  class NestedClass  :  ScriptableObject
  8. {
  9.      [SerializeField ]
  10.      private  float m_StructFloat;
  11.  
  12.      public  void  OnEnable ( )  {  hideFlags  = HideFlags. HideAndDontSave}
  13.  
  14.      public  void  OnGUI ( )
  15.      {
  16.         m_StructFloat  =  EditorGUILayout. FloatField ( "Float", m_StructFloat );
  17.      }
  18. }
  19.  
  20. [Serializable ]
  21. public  class SerializeMe
  22. {
  23.      [SerializeField ]
  24.      private NestedClass m_Class1;
  25.  
  26.      [SerializeField ]
  27.      private NestedClass m_Class2;
  28.  
  29.      public SerializeMe  ( )
  30.      {
  31.         m_Class1  =  ScriptableObject. CreateInstance <NestedClass >  ( );
  32.         m_Class2  = m_Class1;
  33.      }
  34.  
  35.      public  void  OnGUI  ( )
  36.      {
  37.         m_Class1. OnGUI ( );
  38.         m_Class2. OnGUI ( );
  39.      }
  40. }
  41.  
The three changes of note here are that:
  • NestedClass is now a ScriptableObject.
  • We create an instance using the CreateInstance<> function instead of calling the constructor.
  • We also set the hide flags... this will be explained later

These simple changes mean that the instance of the NestedClass will only be serialized once, with each of the references to the class pointing to the same one.

ScriptableObject Initialization
So now we know that for complex data structures where external referencing is needed it is a good idea to use ScriptableObjects. But what is the correct way to work with ScriptableObjects from user code? The first thing to examine is HOW scriptable objects are initialized, especially from the Unity serialization system.
  1. The constructor is called on the ScriptableObject
  2. Data is serialized into the object from the c++ side of unity (if such data exists)
  3. OnEnable() is called on the ScriptableObject

Working with this knowledge there are some things that we can say:
  • Doing initialization in the constructor isn’t a very good idea as data will potentially be overridden by the serialization system.
  • Serialization happens AFTER construction, so we should do our configuration stuff after serialization.
  • OnEnable() seems like the best candidate for initialization.

Lets make some changes to the ‘SerializeMe’ class so that it is a ScriptableObject. This will allow us to see the correct initialization pattern for ScriptableObjects.

Code (csharp):
  1.  
  2. // also updated the Window to call CreateInstance instead of the constructor
  3. using System;
  4. using UnityEngine;
  5.  
  6. [Serializable ]
  7. public  class SerializeMe  :  ScriptableObject
  8. {
  9.      [SerializeField ]
  10.      private NestedClass m_Class1;
  11.  
  12.      [SerializeField ]
  13.      private NestedClass m_Class2;
  14.  
  15.      public  void  OnEnable  ( )
  16.      {
  17.          hideFlags  = HideFlags. HideAndDontSave;
  18.          if  (m_Class1  ==  null )
  19.          {
  20.             m_Class1  = CreateInstance <NestedClass >  ( );
  21.             m_Class2  = m_Class1;
  22.          }
  23.      }
  24.  
  25.      public  void  OnGUI  ( )
  26.      {
  27.         m_Class1. OnGUI ( );
  28.         m_Class2. OnGUI ( );
  29.      }
  30. }
  31.  
On the surface it seems that we have not really changed this class much, it now inherits from ScriptableObject and instead of using a constructor has an OnEnable(). The important part to take note of is slightly more subtle... OnEnable() is called AFTER serialization; because of this we can see if the [SerializedFields] are null or not. If they are null it indicates that this the first initialization, and we need to construct the instances. If they are not null then they have been loaded into memory, and do NOT need to be constructed. It is common in OnEnable() to also call a custom Initialization function to configure any private / non serialized fields on the object, much like you would do in a constructor.

HideFlags
In the examples using ScriptableObjects you will notice that we are setting the ‘hideFlags’ on the object to HideFlags.HideAndDontSave. This is a special setup that is required when writing custom data structures that have no root in the scene. This is to get around how the Garbage Collector works in Unity. 

When the garbage collector is run it (for the most part) uses the scene as ‘the root’ and traverses the hierarchy to see what can get GC’d. Setting the HideAndDontSave flag on a ScriptableObject tells Unity to consider that object as a root object. Because of this it will not just disappear because of a GC / assembly reload. The object can still be destroyed by calling Destroy().

Some ScriptableObject Rules
  • ScriptableObjects will only be serialized once, allowing you to use references properly
  • Use OnEnable to initialize ScriptableObjects
  • Don’t ever call the constructor of a ScriptableObject, use CreatInstance instead.
  • For nested data structures that are only referenced once don’t use ScriptableObject as they have more overhead.
  • If your scriptable object is not rooted in the scene set the hideFlags to HideAndDontSave

Concrete Array Serialization
Lets have a look at a simple example that serializes a range of concrete classes.

Code (csharp):
  1.  
  2. using System;
  3. using System. Collections. Generic;
  4. using UnityEditor;
  5. using UnityEngine;
  6.  
  7. [Serializable ]
  8. public  class BaseClass
  9. {
  10.      [SerializeField ]
  11.      private  int m_IntField;
  12.      public  void  OnGUI ( )  {m_IntField  =  EditorGUILayout. IntSlider  ( "IntField", m_IntField,  010 ); }
  13. }
  14.  
  15. [Serializable ]
  16. public  class SerializeMe  :  ScriptableObject
  17. {
  18.      [SerializeField ]
  19.      private List <BaseClass > m_Instances;
  20.  
  21.      public  void  OnEnable  ( )
  22.      {
  23.          hideFlags  = HideFlags. HideAndDontSave;
  24.          if  (m_Instances  ==  null )
  25.             m_Instances  =  new List <BaseClass >  ( );
  26.      }
  27.  
  28.      public  void  OnGUI  ( )
  29.      {
  30.          foreach  ( var instance  in m_Instances )
  31.             instance. OnGUI  ( );
  32.  
  33.          if  ( GUILayout. Button  ( "Add Simple" ) )
  34.             m_Instances. Add  ( new BaseClass  ( ) );
  35.      }
  36. }
  37.  
This basic example has a list of BaseClasses, by clicking the ‘Add Simple’ button it creates an instance and adds it to the list. Due to the SerializeMe class being configured properly for serialization (as discussed before) it ‘just works’. Unity sees that the List is marked for serialization and serializes each of the List elements.

General Array Serialization
Lets modify the example to serialize a list that contains members of a base class and child class:

Code (csharp):
  1.  
  2. using System;
  3. using System. Collections. Generic;
  4. using UnityEditor;
  5. using UnityEngine;
  6.  
  7. [Serializable ]
  8. public  class BaseClass
  9. {
  10.      [SerializeField ]
  11.      private  int m_IntField;
  12.      public  virtual  void  OnGUI ( )  { m_IntField  =  EditorGUILayout. IntSlider ( "IntField", m_IntField,  010 )}
  13. }
  14.  
  15. [Serializable ]
  16. public  class ChildClass  : BaseClass
  17. {
  18.      [SerializeField ]
  19.      private  float m_FloatField;
  20.      public  override  void  OnGUI ( )
  21.      {
  22.          base. OnGUI  ( );
  23.         m_FloatField  =  EditorGUILayout. Slider ( "FloatField", m_FloatField, 0f, 10f );
  24.      }
  25. }
  26.  
  27. [Serializable ]
  28. public  class SerializeMe  :  ScriptableObject
  29. {
  30.      [SerializeField ]
  31.      private List <BaseClass > m_Instances;
  32.  
  33.      public  void  OnEnable  ( )
  34.      {
  35.          if  (m_Instances  ==  null )
  36.             m_Instances  =  new List <BaseClass >  ( );
  37.  
  38.          hideFlags  = HideFlags. HideAndDontSave;
  39.      }
  40.  
  41.      public  void  OnGUI  ( )
  42.      {
  43.          foreach  ( var instance  in m_Instances )
  44.             instance. OnGUI  ( );
  45.  
  46.          if  ( GUILayout. Button  ( "Add Base" ) )
  47.             m_Instances. Add  ( new BaseClass  ( ) );
  48.          if  ( GUILayout. Button  ( "Add Child" ) )
  49.             m_Instances. Add  ( new ChildClass  ( ) );
  50.      }
  51. }
  52.  
The example has been extended so that there is now a ChildClass, but we are serializing using the BaseClass. If you create a few instance of the ChildClass and the BaseClass they will render properly. Issues arise when they are placed through an assembly reload. After the reload completes every instance will be a BaseClass, with all the ChildClass information stripped. The instances are being sheared by the serialization system.

The way to work around this limitation of the serialization system is to once again use ScriptableObjects:

Code (csharp):
  1.  
  2. using System;
  3. using System. Collections. Generic;
  4. using UnityEngine;
  5. using UnityEditor;
  6.  
  7. [Serializable ]
  8. public  class MyBaseClass  :  ScriptableObject
  9. {
  10.      [SerializeField ]
  11.      protected  int m_IntField;
  12.  
  13.      public  void  OnEnable ( )  {  hideFlags  = HideFlags. HideAndDontSave}
  14.  
  15.      public  virtual  void  OnGUI  ( )
  16.      {
  17.         m_IntField  =  EditorGUILayout. IntSlider ( "IntField", m_IntField,  010 );
  18.      }
  19. }
  20.  
  21. [Serializable ]
  22. public  class ChildClass  : MyBaseClass
  23. {
  24.      [SerializeField ]
  25.      private  float m_FloatField;
  26.  
  27.      public  override  void  OnGUI ( )
  28.      {
  29.          base. OnGUI  ( );
  30.         m_FloatField  =  EditorGUILayout. Slider ( "FloatField", m_FloatField, 0f, 10f );
  31.      }
  32. }
  33.  
  34. [Serializable ]
  35. public  class SerializeMe  :  ScriptableObject
  36. {
  37.      [SerializeField ]
  38.      private List <MyBaseClass > m_Instances;
  39.  
  40.      public  void  OnEnable  ( )
  41.      {
  42.          if  (m_Instances  ==  null )
  43.             m_Instances  =  new List <MyBaseClass > ( );
  44.  
  45.          hideFlags  = HideFlags. HideAndDontSave;
  46.      }
  47.  
  48.      public  void  OnGUI  ( )
  49.      {
  50.          foreach  ( var instance  in m_Instances )
  51.             instance. OnGUI  ( );
  52.  
  53.          if  ( GUILayout. Button  ( "Add Base" ) )
  54.             m_Instances. Add (CreateInstance <MyBaseClass > ( ) );
  55.          if  ( GUILayout. Button  ( "Add Child" ) )
  56.             m_Instances. Add (CreateInstance <ChildClass > ( ) );
  57.      }
  58. }
  59.  
After running this, changing some values, and reloading assemblies you will notice that ScriptableObjects are safe to use in arrays even if you are serializing derived types. The reason is that when you serialize a standard [Serializable] class it is serialized ‘in place’, but a ScriptableObject is serialized externally and the reference inserted into the collection. The shearing occurs because the type can not be properly be serialized as the serialization system thinks it is of the base type.

Serializing Abstract Classes
So now we have seen that it’s possible to serialize a general list (so long as the members are of type ScriptableObject). Lets see how abstract classes behave:

Code (csharp):
  1.  
  2. using System;
  3. using UnityEditor;
  4. using System. Collections. Generic;
  5. using UnityEngine;
  6.  
  7. [Serializable ]
  8. public abstract  class MyBaseClass  :  ScriptableObject
  9. {
  10.      [SerializeField ]
  11.      protected  int m_IntField;
  12.  
  13.      public  void  OnEnable ( )  {  hideFlags  = HideFlags. HideAndDontSave}
  14.  
  15.      public abstract  void  OnGUI  ( );
  16. }
  17.  
  18. [Serializable ]
  19. public  class ChildClass  : MyBaseClass
  20. {
  21.      [SerializeField ]
  22.      private  float m_FloatField;
  23.  
  24.      public  override  void  OnGUI ( )
  25.      {
  26.         m_IntField  =  EditorGUILayout. IntSlider ( "IntField", m_IntField,  010 );
  27.         m_FloatField  =  EditorGUILayout. Slider ( "FloatField", m_FloatField, 0f, 10f );
  28.      }
  29. }
  30.  
  31. [Serializable ]
  32. public  class SerializeMe  :  ScriptableObject
  33. {
  34.      [SerializeField ]
  35.      private List <MyBaseClass > m_Instances;
  36.  
  37.      public  void  OnEnable  ( )
  38.      {
  39.          if  (m_Instances  ==  null )
  40.             m_Instances  =  new List <MyBaseClass > ( );
  41.  
  42.          hideFlags  = HideFlags. HideAndDontSave;
  43.      }
  44.  
  45.      public  void  OnGUI  ( )
  46.      {
  47.          foreach  ( var instance  in m_Instances )
  48.             instance. OnGUI  ( );
  49.  
  50.          if  ( GUILayout. Button  ( "Add Child" ) )  
  51.             m_Instances. Add (CreateInstance <ChildClass > ( ) );
  52.      }
  53. }
  54.  
This code much like the previous example works. But it IS dangerous. Lets see why.

The function CreateInstance<>() expects a type that inherits from ScriptableObject, the class ‘MyBaseClass’ does in fact inherit from ScriptableObject. This means that it’s possible to add an instance of the abstract class MyBaseClass to the m_Instances array. If you do this and then try and access an abstract method bad things will happen because there is no implementation of that function. In this specific case that would be the OnGUI method.

Using abstract classes as the serialized type for lists and fields DOES work, so long as they inherit from ScriptableObject, but it is not a recommended practice. Personally I think it’s better to use concrete classes with empty virtual methods. This ensures that things will not go bad for you.

From:http://forum.unity3d.com/threads/serialization-best-practices-megapost.155352/

相关推荐
<p style="text-align:left;"> <span> </span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-family:"color:#E53333;font-size:14px;background-color:#FFFFFF;line-height:24px;"><span style="line-height:24px;">限时福利1:</span></span><span style="font-family:"color:#3A4151;font-size:14px;background-color:#FFFFFF;">购课进答疑群专享柳峰(刘运强)老师答疑服务。</span> </p> <p> <br /> </p> <p class="ql-long-24357476"> <strong><span style="color:#337FE5;font-size:14px;">为什么说每一个程序员都应该学习MySQL?</span></strong> </p> <p class="ql-long-24357476"> <span style="font-size:14px;">根据《2019-2020年中国开发者调查报告》显示,超83%的开发者都在使用MySQL数据库。</span> </p> <p class="ql-long-24357476"> <img src="https://img-bss.csdn.net/202003301212574051.png" alt="" /> </p> <p class="ql-long-24357476"> <span style="font-size:14px;">使用量大同时,掌握MySQL早已是运维、DBA的必备技能,甚至部分IT开发岗位也要求对数据库使用和原理有深入的了解和掌握。</span><br /> <br /> <span style="font-size:14px;">学习编程,你可能会犹豫选择 C++ 还是 Java;入门数据科学,你可能会纠结于选择 Python 还是 R;但无论如何, MySQL 都是 IT 从业人员不可或缺的技能!</span> </p> <span></span> <p> <br /> </p> <p> <span> </span> </p> <h3 class="ql-long-26664262"> <p style="font-size:12pt;"> <strong class="ql-author-26664262 ql-size-14"><span style="font-size:14px;color:#337FE5;">【课程设计】</span></strong> </p> <p style="font-size:12pt;"> <span style="color:#494949;font-weight:normal;"><br /> </span> </p> <p style="font-size:12pt;"> <span style="color:#494949;font-weight:normal;font-size:14px;">在本课程中,刘运强老师会结合自己十多年来对MySQL的心得体会,通过课程给你分享一条高效的MySQL入门捷径,让学员少走弯路,彻底搞懂MySQL。</span> </p> <p style="font-size:12pt;"> <span style="color:#494949;font-weight:normal;"><br /> </span> </p> <p style="font-size:12pt;"> <span style="font-weight:normal;font-size:14px;">本课程包含3大模块:</span><span style="font-weight:normal;font-size:14px;"> </span> </p> </h3> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <strong class="ql-author-26664262"><span style="font-size:14px;">一、基础篇:</span></strong> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <span class="ql-author-26664262" style="font-size:14px;">主要以最新的MySQL8.0安装为例帮助学员解决安装与配置MySQL的问题,并对MySQL8.0的新特性做一定介绍,为后续的课程展开做好环境部署。</span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <span class="ql-author-26664262" style="font-size:14px;"><br /> </span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <strong class="ql-author-26664262"><span style="font-size:14px;">二、SQL语言篇</span></strong><span class="ql-author-26664262" style="font-size:14px;">:</span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <span class="ql-author-26664262" style="font-size:14px;">本篇主要讲解SQL语言的四大部分数据查询语言DQL,数据操纵语言DML,数据定义语言DDL,数据控制语言DCL,</span><span style="font-size:14px;">学会熟练对库表进行增删改查等必备技能。</span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;"><br /> </span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <strong class="ql-author-26664262"><span style="font-size:14px;">三、MySQL进阶篇</span></strong><span style="font-size:14px;">:</span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;">本篇可以帮助学员更加高效的管理线上的MySQL数据库;具备MySQL的日常运维能力,语句调优、备份恢复等思路。</span> </p> <span><span> <p style="font-size:11pt;color:#494949;"> <span style="font-size:14px;"> </span><img src="https://img-bss.csdn.net/202004220208351273.png" alt="" /> </p> </span></span>
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页